<!DOCTYPE html>


<html lang="zh-CN">
  

    <head>
      <meta charset="utf-8" />
       
      <meta name="keywords" content="陈亚东 cyd 凤舞神者" />
       
      <meta name="description" content="85年的精神小伙,不是神者有点神经,水尿吧汤 " />
      
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title> 风舞神者</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/favicon.ico" />
       
<link rel="stylesheet" href="/dist/main.css">

      
<link rel="stylesheet" href="/css/fonts/remixicon.css">

      
<link rel="stylesheet" href="/css/custom.css">
 
      <script src="https://cdn.staticfile.org/pace/1.2.4/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    </head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      
<section class="cover">
    
      
      <a class="forkMe" href="https://github.com/Shen-Yu/hexo-theme-ayer"
        target="_blank"><img width="149" height="149" src="/images/forkme.png"
          class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
    
  <div class="cover-frame">
    <div class="bg-box">
      <img src="/images/cover1.jpg" alt="image frame" />
    </div>
    <div class="cover-inner text-center text-white">
      <h1><a href="/">风舞神者</a></h1>
      <div id="subtitle-box">
        
        <span id="subtitle"></span>
        
      </div>
      <div>
        
      </div>
    </div>
  </div>
  <div class="cover-learn-more">
    <a href="javascript:void(0)" class="anchor"><i class="ri-arrow-down-line"></i></a>
  </div>
</section>



<script src="https://cdn.staticfile.org/typed.js/2.0.12/typed.min.js"></script>


<!-- Subtitle -->

  <script>
    try {
      var typed = new Typed("#subtitle", {
        strings: ['面朝大海，春暖花开', '愿你一生努力，一生被爱', '想要的都拥有，得不到的都释怀'],
        startDelay: 0,
        typeSpeed: 200,
        loop: true,
        backSpeed: 100,
        showCursor: true
      });
    } catch (err) {
      console.log(err)
    }
  </script>
  
<div id="main">
  <section class="outer">
  
  <ul class="ads">
    
        <li>
            <a target="_blank" rel="noopener" href="https://curl.qcloud.com/kvO7hb43">
                <img src="https://pic.imgdb.cn/item/62174b452ab3f51d912a5ccc.jpg" width="300" alt="云服务器限时秒杀">
            </a>
        </li>
    
        <li>
            <a target="_blank" rel="noopener" href="https://www.vultr.com/?ref=8630075">
                <img src="https://pic.imgdb.cn/item/62174b452ab3f51d912a5cd7.png" width="300" alt="vultr优惠vps">
            </a>
        </li>
    
</ul>
  
  
  

<div class="notice" style="margin-top:50px">
    <i class="ri-heart-fill"></i>
    <div class="notice-content" id="broad"></div>
</div>
<script type="text/javascript">
    fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
            document.getElementById("broad").innerHTML = data.hitokoto;
        })
        .catch(console.error)
</script>

<style>
    .notice {
        padding: 20px;
        border: 1px dashed #e6e6e6;
        color: #969696;
        position: relative;
        display: inline-block;
        width: 100%;
        background: #fbfbfb50;
        border-radius: 10px;
    }

    .notice i {
        float: left;
        color: #999;
        font-size: 16px;
        padding-right: 10px;
        vertical-align: middle;
        margin-top: -2px;
    }

    .notice-content {
        display: initial;
        vertical-align: middle;
    }
</style>
  
  <article class="articles">
    
    
    
    
    <article
  id="post-电池管理系统BMS知识与功能入门篇"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/07/07/%E7%94%B5%E6%B1%A0%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9FBMS%E7%9F%A5%E8%AF%86%E4%B8%8E%E5%8A%9F%E8%83%BD%E5%85%A5%E9%97%A8%E7%AF%87/"
    >电池管理系统BMS知识与功能入门篇</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/07/07/%E7%94%B5%E6%B1%A0%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9FBMS%E7%9F%A5%E8%AF%86%E4%B8%8E%E5%8A%9F%E8%83%BD%E5%85%A5%E9%97%A8%E7%AF%87/" class="article-date">
  <time datetime="2023-07-07T02:11:37.000Z" itemprop="datePublished">2023-07-07</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><img src="https://cydmacro.gitee.io/myblog/img/v2-2411fcf6dd068a54b578745424d6335b_1440w-20230707101304840.jpeg" alt="img"></p>
<h2 id="1-什么是BMS？"><a href="#1-什么是BMS？" class="headerlink" title="1)什么是BMS？"></a><strong>1)什么是BMS？</strong></h2><p>BMS全称是Battery Management System，<strong>电池管理系统</strong>。它是配合监控储能电池状态的设备，主要就是为了智能化管理及维护各个电池单元，防止电池出现过充电和过放电，延长电池的使用寿命，监控电池的状态。一般BMS表现为一块电路板，或者一个硬件盒子。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/v2-f435eae429316c963c598b31cfb8072f_1440w-20230707101321322.webp" alt="img"></p>
<p><img src="https://pic4.zhimg.com/80/v2-863ad27c511f3782d07346cec5c6caa3_1440w.webp" alt="img"></p>
<p>BMS是电池储能系统的核心子系统之一，负责监控电池储能单元内各电池运行状态，保障储能单元安全可靠运行。BMS能够实时监控、采集储能电池的状态参数（包括但不限于单体电池电压、电池极柱温度、电池回路电流、电池组端电压、电池系统绝缘电阻等），并对相关状态参数进行必要的分析计算，得到更多的系统状态评估参数，并根据特定保护控制策略实现对储能电池本体的有效管控，保证整个电池储能单元的安全可靠运行。同时BMS可以通过自身的通信接口、模拟&#x2F;数字输入输入接口与外部其他设备（PCS、EMS、消防系统等）进行信息交互，形成整个储能电站内各子系统的联动控制，确保电站安全、可靠、高效并网运行。</p>
<h2 id="2-架构"><a href="#2-架构" class="headerlink" title="2)架构"></a><strong>2)架构</strong></h2><p>从拓扑架构上看，BMS根据不同项目需求分为了集中式（Centralized）和分布式（Distributed）两类。</p>
<p><img src="https://pic4.zhimg.com/80/v2-0a51d26841408dabee3a1dbb7feece37_1440w.webp" alt="img"></p>
<p>集中式BMS<br><strong>简单来说，集中式BMS将所有电芯统一用一个BMS硬件采集，适用于电芯少的场景。</strong><br>集中式BMS具有成本低、结构紧凑、可靠性高的优点，一般常见于容量低、总压低、电池系统体积小的场景中，如电动工具、机器人（搬运机器人、助力机器人）、IOT智能家居（扫地机器人、电动吸尘器）、电动叉车、电动低速车（电动自行车、电动摩托、电动观光车、电动巡逻车、电动高尔夫球车等）、轻混合动力汽车。<br>集中式架构的BMS硬件可分为高压区域和低压区域。高压区域负责进行单体电池电压的采集、系统总压的采集、绝缘电阻的监测。低压区域包括了供电电路、CPU电路、CAN通信电路、控制电路等。<br>随着乘用车动力电池系统不断向高容量、高总压、大体积的方面发展，在插电式混动、纯电动车型上主要还是采用分布式架构的BMS。<br>分布式BMS<br>目前行业内分布式BMS的各种术语五花八门，不同的公司，不同的叫法。动力电池BMS大多是主从两层架构：</p>
<p><img src="https://pic2.zhimg.com/80/v2-97a3ec1b1bec6689f9bac1ae9a9a8015_1440w.webp" alt="img"></p>
<p>储能BMS则因为电池组规模庞大，大多都是<strong>三层架构，</strong>在从控、主控之上，还有一层总控<strong>。</strong></p>
<p><img src="https://pic4.zhimg.com/80/v2-89bdd073dde0b2f0aae4813193037c83_1440w.webp" alt="img"></p>
<p>就像电池构成电池簇、电池簇构成电堆；三层BMS中也遵循这样层层向上的规律：</p>
<ul>
<li><p>从控：电池单体管理单元：BMU（battery module unit，大多都叫BMU，也有的叫CSC&#x2F;CSU），采集单体电池信息。</p>
</li>
<li><ul>
<li>监测电池单体电压、温度</li>
<li>包内电池均衡</li>
<li>信息上送</li>
<li>热管理</li>
<li>异常报警</li>
</ul>
</li>
<li><p>主控：电池簇管理单元：BCU（battery cluster unit，也有高压管理单元HVU、BCMU等等），负责收集BMU信息，并采集电池簇信息。</p>
</li>
<li><ul>
<li>电池簇电流采集，总电压采集，漏电检测</li>
<li>电池状态异常时断电保护</li>
<li>在BMS的管理下可单独完成容量标定和SOC标定，作为后续充放电管理的依据</li>
</ul>
</li>
<li><p>总控：电池阵列管理单元：BAU（battery array unit，也有叫BAMS、MBMS等等），对整个储能电池堆的电池进行集中管理。向下连接各个电池簇管理单元，向上与其他设备信息交互，反馈电池阵列的运行状态信息。</p>
</li>
<li><ul>
<li>电池阵列的充放电管理</li>
<li>BMS系统自检与故障诊断报警</li>
<li>电池组故障诊断报警</li>
<li>电池阵列内各种异常及故障情况的安全保护</li>
<li>与PCS、EMS等其他设备通信</li>
<li>数据存储、传输与处理</li>
</ul>
</li>
</ul>
<p><strong>单体电池管理层：</strong>负责采集电池的各种单体信息（电压、温度），计算分析电池的SOC和SOH，实现对单体电池的主动均衡，并将单体异常信息上传给电池组单元层BCMU。通过CAN对外通信，通过菊花链相互连接。<br><strong>电池组管理层：</strong>负责收集BMU上传的各种单体电池信息，采集电池组的各种信息（组电压、组温度）、电池组充电放电电流等，计算分析电池组的SOC和SOH，并将所有信息上传给电池簇单元层BAMS。通过CAN对外通信，通过菊花链相互连接。<br><strong>电池簇管理层：</strong>负责收集BCMU上传的各种电池信息，并将所有信息以RJ45接口上传给储能监控EMS系统；与PCS通信，将电池的相关异常信息发送给PCS（CAN或RS485接口），且配有硬件干节点对PCS。此外进行电池系统BSE(Battery State Estimate)评估、电系统状态检测、接触器管理、热管理、运行管理、充电管理、诊断管理、以及执行对内外通信网络的管理。通过CAN与下级进行通信。</p>
<h2 id="3-BMS是做什么的？"><a href="#3-BMS是做什么的？" class="headerlink" title="3)BMS是做什么的？"></a>3)BMS是做什么的？</h2><p>BMS的功能有很多，最核心的、我们最关注的，无非就是三个方面：<br>一个是<strong>感知（状态管理）</strong>，感知电池的状态，这就是BMS的基本功能，不管测电压、测电阻、测温度，最后就是一个感知电池状态，我们想知道电池状态什么样，现在也多少能量，多少容量，现在健康状态怎么样，现在有多少功率，现在安全状态怎么样，这就是感知。<br>第二个就是<strong>管理（均衡管理）</strong>，有人说BMS是电池的保姆，那这种保姆就要去管理，管理什么，就要把这个电池尽可能用好它，最基本就是均衡管理、热管理。<br>第三个是<strong>保护（安全管理）</strong>，保姆还要做一个工作，如果电池出了一些状态，它要去进行保护并向上报警。<br>当然最后附带一块<strong>通信管理，</strong>通过一定的规约在系统内，或系统外传递数据。<br>BMS还有很多其他功能，例如运行控制、绝缘监测、热管理等等，这里不展开介绍。</p>
<p><img src="https://pic4.zhimg.com/80/v2-40edbc5b6792887d46879493d6e78b83_1440w.webp" alt="img"></p>
<p>3.1 <strong>感知——测量与估算</strong><br>BMS的基本功能就是对电池参数的测量和估算，包括电压、电流、温度等基本参数、状态，以及SOC、SOH等电池状态数据的计算。动力电池领域还涉及SOP（state of power）、SOE（state of energy）的计算，这里不展开讲，着重讲前两个应用更广泛的数据。</p>
<p>电芯测量<br>1)基本信息测量：电池管理系统有着最基本功能就是测量电池单体的<strong>电压，电流和温度</strong>，这是所有电池管理系统顶层计算、控制逻辑的基础 。<br>2)绝缘电阻检测：电池管理系统内需要对整个电池系统和高压系统进行绝缘检测。<br>3)高压互锁检测(HVIL)：用来确认整个高压系统的完整性的，当高压系统回路完整性受到破坏的时候启动安全措施。</p>
<p>SOC计算<br>SOC，指State of Charge，电池剩余容量。简单来说，就是电池还剩下多少电。<br>SOC是BMS中最重要的参数，因为其它一切都是以SOC为基础的，所以它的精度和鲁棒性（也叫纠错能力）极其重要。如果没有精确的SOC，再多的保护功能也无法使BMS正常工作，因为电池会经常处于被保护状态，更无法延长电池的寿命。<br>目前主流的SOC估算方法有开路电压法、电流积分法、卡尔曼滤波法和神经网络法。比较常用的是前两种。后两种涉及积分啊模型啊人工智能啊等等高尖深的知识，这里不详细介绍。<br>实际应用中常常是多种算法混合使用，根据电池充放电状态的不同采用不同算法。<br>开路电压法<br>开路电压法其原理是利用电池在长时间静置的条件下，开路电压与SOC存在相对固定的函数关系，从而根据开路电压来估算SOC。以前常用的铅蓄电池电动自行车就是使用这种方法估算SOC。开路电压法简单便捷，但也存在很多缺点：<br>\1. 电池须经过长期静置，否则开路电压短时间内很难稳定；<br>\2. 电池存在电压平台，特别是磷酸铁锂电池，在SOC30%-80%期间，端电压和SOC曲线近似为直线；<br>\3. 电池处于不同温度或不同寿命时期，尽管开路电压一样，但实际上的SOC差别可能较大；<br>如下图，我们在使用这种电动自行车，假如当前SOC显示100%，在加速启动下电压下降，电量可能显示80%，停止加速时电压回升，电量又会跳回100%。所以我们的小电车电量显示不准，停下来有电开起来就没电了，可能不是电池的问题，而是BMS的SoC算法太简单的锅。</p>
<p><img src="https://pic1.zhimg.com/80/v2-5c7256a552dc71c8a88d166f208dd3dc_1440w.webp" alt="img"></p>
<p>安时积分法<br>安时积分法，直接通过SOC的定义来实时计算SOC值。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/v2-dba0569ba3dacd3ca02997b188bc0750_1440w-20230707101418348.webp" alt="img"></p>
<p>在已知SOC初始值的前提下，只要可以测量出电池的电流（式中放电电流为正），通过电流积分，可以准确地计算出电池电量的变化情况，进而得到剩余电量SOC。<br>该方法在短时间内的估计结果比较可靠，但由于电流传感器的测量误差以及电池容量的逐渐衰退，长时间的电流积分会引入一定的偏差。所以其一般与开路电压法估计初始值配合，用于精度要求不高的SOC估计，也可以与卡尔曼滤波法配合，用于短时的SOC预测。<br>SOC(StateOfCharge)属于BMS核心控制算法，表征当前的剩余容量状态，主要通过安时积分法和EKF(扩展卡尔曼滤波)算法，并结合修正策略(如开路电压修正，充满修正，充电末端修正，不同温度及SOH下的容量修正等)。安时积分法在保证电流采集精度条件下比较可靠，但鲁棒性不强，由于存在误差累计必须结合修正策略，而EKF鲁棒性较强，但算法比较复杂，实现难度大。国内主流厂家一般常温可以做到精度6%以内，在高低温和电池衰减时的估算是难点。<br>SOC修正<br>由于电流波动会导致SOC估算偏差较大，计算出的SOC可能不够可靠，估算时还需要结合各种各样的修正策略。</p>
<p>SOH计算<br>SOH，指State of Health，电池健康状态（或电池劣化程度）。主要表征当前电池的健康状态，为0-100%之间数值，一般认为低于80%以后电池便不可再用。可以用电池容量或内阻变化来表示，用容量时即通过电池运行过程数据估算出当前电池的实际容量，与额定容量的比值即为SOH。准确的SOH会提高电池衰减时其他模块的估算精度。</p>
<p>对SOH的定义，在业界有两种不同的定义方式：<br><strong>基于容量衰减的SOH定义（Capacity Fade）</strong><br>锂离子电池在使用过程中，电池内部活性物质逐渐减少，内阻增加，容量衰减，因此可以通过电池容量来估计SOH。电池的健康状态表述为电池当前容量与初始容量的比值，其SOH定义为：<br>SOH&#x3D;(C_standard-C_fade)&#x2F;C_standard ×100%<br>式中：C_fade为电池已损失容量；C_standard为标称容量。<br>IEEE标准1188-1996中规定，动力电池容量能力下降到80%时，就应该更换电池。因此通常我们认为电池SOH低于80%后便不可用。<br><strong>基于功率衰减的SOH定义（Power Fade）</strong><br>几乎所有类型的电池的老化都会导致电池内阻的增加，电池的内阻越高，其可用的功率就越小。因此，可以用功率的衰减来估计SOH。</p>
<p><strong>3.2 管理——均衡技术</strong><br>每个电池都有自己的“个性”<br>要说均衡，得先从电池谈起。即使是同一厂家同一批次生产的电池，也都有自己的生命周期、自己的“个性”——<strong>每个电池的容量不可能完全一致</strong>。这种不一致性有两类原因：</p>
<ul>
<li>一类是电芯生产的不一致性</li>
<li>一类是电化学反应的不一致性</li>
</ul>
<p><strong>生产不一致性</strong><br>生产不一致性很好理解，比如在生产过程中，隔膜不一致，阴极，阳极材料的不一致，造成整体电池容量的不一致，标准是一个50AH的电池，可能一个变成了49AH，一个变成了51AH。<br><strong>电化学不一致性</strong><br>电化学的不一致性就是在电池充放电的过程中，即使两个电芯的生产加工一模一样，但是热环境在电化学反应的过程中是永远不可能一致的，比如做电池模组的时候，周围一圈温度肯定比中间要低。这就造成充电量、放电量的长久不一致，这也就造成电芯容量不一致；以及电芯SEI膜在长时间充放电电流不一致的时候，SEI膜衰老也就不一致。<br>*SEI膜：“固体电解质界面膜”( solid electrolyte interface)，在液态锂离子电池首次充放电过程中，电极材料与电解液在固液相界面上发生反应，形成的覆盖于电极材料表面的钝化层。SEI膜是电子绝缘体却是锂离子的优良导体，在保护电极的同时不影响电池功能，SEI膜衰老对电池健康有很大影响。<br>所以电池组非一致性（或者离散性）是电池运行的一种必然表现。</p>
<p>为什么需要均衡<br>各个电池不一样就不一样，为什么非要想办法让他们一样呢？因为<strong>不一致性会影响电池组的性能</strong>。<br>串联成组的电池组遵循木桶短板效应：<strong>在串联成组的电池组系统中，整个电池组系统的容量由容量最小的单体决定。</strong><br>假如我们有一个3节电池构成的电池组：</p>
<p><img src="https://pic4.zhimg.com/80/v2-0fa8d4ae26538da527a5f23b244162ef_1440w.webp" alt="img"></p>
<p>我们知道过充过放对电池的伤害很大。所以当充电时电池B已经充满，或者放电时电池B的SoC已经很低，就需要停止充放电，保护电池B，电池A和电池C的电量就无法被充分利用。<br>这就导致：</p>
<ol>
<li><strong>电池组实际可用容量降低：</strong>电池A和C本来可以使用的容量，现在为了照顾B而无处发力，就像二人三足把高个和矮个绑在一起，高个的步子就无法迈得很大。</li>
<li><strong>电池组寿命降低：</strong>步幅小了，需要走的步数就多了，腿就更累；容量降低了，需要充放电的循环次数就增加了，电池的衰减也更大。比如单个电芯在100%DoD的情况下能达到4000次循环，但实际使用中无法达到100%，循环次数一定达不到4000次。</li>
</ol>
<p>*DoD， Depth of discharge，放电深度，表示电池放电量与电池额定容量的百分比。<br>电池的不一致性导致了电池组的性能降低，电池模组的规模较大时，多串电池串联，较大的单体电压差将造成整个箱体的容量下降，串联电池越多，其损失的容量越多。而我们在应用中，尤其是储能系统应用中，有两个重要的要求：<br>第一个<strong>长寿命</strong>，长寿命的电池可以大大降低运维成本，储能系统对电池组寿命提出了很高的要求，国内大部分在提15年的寿命，如果一年算300次循环，15年就是4500次，这个要求还是非常高的。我们需要每一个电池尽可能发挥应有的寿命，使得整个电池组总的寿命尽可能达到设计的寿命，减少电池离散对电池组寿命的影响。<br>第二个<strong>深循环</strong>，尤其是在削峰填谷应用场景，多放出一度电多一分收益，也就是说我们会做80%DoD或90%DoD，储能系统当中用到这个深度的时候，尾部放电时候电池的离散性就会表现出来，所以为了保证电池组在深充深放条件下每个单体容量的充分释放，必须要求储能BMS具有很强的均衡管理能力，抑制电池单体间一致性的出现。<br>这两个要求，正好与电池不一致性相悖，我们要达到更高效的电池组应用，就势必需要更有效的均衡技术，来削弱电池不一致性的影响。</p>
<p>均衡技术<br>电池均衡技术，就是想办法让容量不一样的电池变得一样。常见的均衡方式有两种：能量耗散型单向均衡（被动均衡）和能量转移型双向均衡（主动均衡）。</p>
<p><strong>（1）被动均衡</strong><br>被动均衡原理是在每串电池上并联一个可以开关的放电电阻，BMS控制放电电阻对电压较高的单体放电，电能以热的形式<strong>耗散</strong>掉。例如当电池B快充满时，打开开关让电池B上的电阻放热，让B多余的电能以热能形式耗散，再继续充电，直到A和C也充满。</p>
<p>这种方式<strong>只能对电压高的单体放电，不能对容量低的单体进行补充电</strong>，受放电电阻功率限制，均衡电流一般较小（小于1A）。<br>被动均衡的优点是成本低和电路设计简单；而缺点为是以最低电池残余量为基准进行均衡，无法增加残量少的电池的容量，及均衡电量100％以热量形式被浪费。</p>
<p><strong>（2）主动均衡</strong><br>多串的电池之间通过算法借助储能元器件将电压高的电芯的能量转移给低电压电芯，对电压较高的电池放电，放出的能量用来对电压较低的单体进行充电，能量主要是<strong>转移</strong>而不是耗散。<br>这样，在充电时，让电压最先达到100%的电池B自放电给A和C，三个电池再一起充满；放电时也是一样，当电池B剩余电量过低时，让A和C给B“充电”，让电芯B不会那么快触及停止放电的SOC阈值。</p>
<p><img src="https://pic3.zhimg.com/80/v2-83b1db5542db753de0034f4eda644636_1440w.webp" alt="img"></p>
<p><strong>主动均衡技术主要特点</strong><br>（1）均衡削高补低，提高电池组的使用效率：在充放电及静止过程中，均可以对电压高的电池放电，对电压低的电池充电；<br>（2）低损耗能量转移：能量主要是转移，而非单纯的损耗，提高了电能的利用效率；<br>（3）均衡电流较大：一般均衡电流在1～10A，均衡更快；<br>主动均衡需要配置相应电路和储能器件，体积大，成本上升，这两个条件一起决定了主动均衡不容易推广应用。<br>另外，主动均衡的充放过程，无形中增加了电池的循环次数，对于本身需要充放电才能实现均衡的电芯，额外的工作量可能造成其超越一般电芯的老化，进而造成与其他电芯更大的性能差距。</p>
<p>有专家认为，上面的两个表述应该对应于耗散型均衡和非耗散型均衡。而主动还是被动，应该取决于触发均衡过程的事件，系统到达那个状态不得不进行的就是被动。如果是人为设定，在可以不均衡的时候设置了均衡程序，才称为主动均衡。<br>例如，放电放到最后，电压最低的电芯已经到达了放电截止电压，而其他电芯还存有电量。这时候，系统为了把尽量多的电都放掉，于是把高能量电芯的电部分的转移给低能量的电芯，使得放电过程又进行下去，直到把全部电量放干净，这是被动均衡过程。如果在放电至电量还有40%的时候，系统预计到，在放电截止的时候会出现不均衡，于是起动均衡过程，这才是主动均衡。</p>
<p>主动均衡分为集中式和分散式。集中式的均衡方法就是从整组的电池获取能量，然后通过电能转化装置向能量少的电池补充能量，分散式的均衡是在相邻的电池之间存在一个储能环节，这个储能环节可以是电感，也可以是电容，这样就可以让能量在相邻的电池之间流动。</p>
<p>当前的均衡控制策略中，有以单体电压为控制目标参数的，也有人提出应该用SOC作为均衡控制目标参数。以单体电压为例。</p>
<ul>
<li>首先设定一对启动和结束均衡的阈值：例如一组电池中，单体电压极值与这组电压平均值的差值达到50mV时启动均衡，5mV结束均衡。</li>
<li>BMS按照固定的采集周期采集单体电压，计算平均值，再计算每个单体电压与均值的差值；</li>
<li>如果最大的一个差值达到了50mV，BMS就需要启动均衡程序；</li>
<li>在均衡过程中持续步骤2，直到差值都小于5mV，结束均衡。</li>
</ul>
<p>需要注意的是，不一定所有BMS都是这个步骤，后续策略根据均衡方式的不同可能有所不同。</p>
<p>均衡技术与电池种类也有一定的关系，一般认为LFP更加适合主动均衡，三元电池适合被动均衡。</p>
<p>BMS进行白热化竞争的阶段，大部分都是靠成本与可靠性来支撑，目前主动均衡的实验验证尚未实现，功能安全的等级想要往ASIL-C, ASIL-D走，付出的代价相当之大，所以目前的大公司对于主动均衡研究，都处于慎重态度，甚至有一些大厂，要取消均衡模块，所有的均衡在外部进行，就类似与燃油车的保养，每开多少公里，去4S进行一次外部的均衡，这样整车BMS成本降低，而且对应的4S店也有收益，属于多方共赢，所以个人理解，这个可能会成为一个趋势！</p>
<p><strong>3.3 保护——故障诊断和报警</strong><br>BMS监控与电气系统硬件匹配，针对电池的不同表现情况，区分为不同的故障等级（轻微故障、严重故障、致命故障），并且在不同故障等级情况下采取不同的处理措施：警告，限功率或直接切断高压。故障包括数据采集及合理性故障、电气故障(传感器和执行器)、通讯故障及电池状态故障等。<br>一个常见的例子是电池过热时，BMS根据采集上来的电池温度，判断出电池过热，随后控制此电池的电路断开，进行过热保护，并向EMS等管理系统发出告警。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/v2-de7e77c275f75439aa641cbe26926cb9_1440w-20230707101457882.webp" alt="img"></p>
<p><strong>3.4 通信</strong><br>BMS的正常工作离不开BMS的通信功能，无论是进行电池管理时控制电池，还是向外传输电池状况、接受控制指令，都需要稳定的通信。<br>在动力电池系统中，BMS一端与电池相连，另一端又与整车的控制及电子系统相连接，大环境都采用<strong>CAN协议</strong>，只是按照电池包内部组件之间使用内部CAN，电池包与整车之间使用整车CAN做区分。<br>相比之下，储能BMS与内部的通讯基本都采用CAN协议，但其与外部通讯（外部主要指储能电站调度系统PCS）往往采用互联网协议格式<strong>TCP&#x2F;IP协议</strong>和<strong>modbus协议</strong>。</p>
<h2 id="4-储能BMS"><a href="#4-储能BMS" class="headerlink" title="4)储能BMS"></a>4)储能BMS</h2><p>储能BMS厂商一般从动力电池BMS发展而来，因此，很多设计和名词有历史沿革<br>比如动力电池里一般分为 BMU（Battery Monitor Unit） 和BCU（Battery Control Unit）前者采集，后者控制。<br>因为电芯是一个电化学的过程，多个电芯组成一个电池，由于每个电芯特性，无论制造多精密，随使用时间，环境，各个电芯都会存在误差与不一致的地方，故电池管理系统，就是通过有限的参数，去评估当前电池的状态，有点像中医看病，通过表征，看你得了啥病，不是西医，需要一些理化分析，人体的理化分析就像电池的电化学特性，可以通过大型试验仪器去测量，但是嵌入式系统很难去评估电化学的一些指标，故BMS就是一个老中医。</p>
<p><strong>4.1储能BMS三层架构</strong><br>储能系统由于电池单体众多，为了节约成本，一般BMS分层实现，有2层和3层之分。目前主流是三层：总控&#x2F;主控&#x2F;从控。</p>
<p><img src="https://pic4.zhimg.com/80/v2-89bdd073dde0b2f0aae4813193037c83_1440w.webp" alt="img"></p>
<p><strong>4.2储能BMS详细说明</strong></p>
<p><img src="https://cydmacro.gitee.io/myblog/img/v2-6c804af93c463e650b6844cd51a5ac35_1440w.webp" alt="img"></p>
<h2 id="5-现状与未来趋势"><a href="#5-现状与未来趋势" class="headerlink" title="5)现状与未来趋势"></a>5)现状与未来趋势</h2><p>入局BMS制造的厂商有几类：<br>第一类是动力电池BMS中最具主导能力的终端用户——车厂，事实上国外BMS制造实力最强的也就是车厂，如通用、特斯拉等；国内有比亚迪、华霆动力等。<br>第二类是电池厂，包含电芯厂商与做pack的厂商，如三星、宁德时代、欣旺达、德赛电池、拓邦股份、北京普莱德等；<br>第三类专业的BMS制造商，此类厂商多有多年的电力电子技术积累，有高校背景或相关企业背景的研发团队，如亿能电子、杭州高特电子、协能科技、科工电子等企业。<br>与动力电池的 BMS 主要由终端车厂主导不同，目前看来储能电池的终端用户没有加入 BMS 研发与制造的需求与具体行动，也不大可能花费大量资金与精力开发大型电池管理系统，所以可以认为储能电池 BMS 行业缺乏一个占据了绝对优势的重要参与者，给电池厂以及专注做储能 BMS 的厂商留下了巨大的发展空间与想象空间。如果储能的市场一旦确立，将给予电池厂与专业 BMS 生产厂商以非常大的发挥空间与较少的竞争阻力。<br>当前专注于储能BMS开发的专业BMS厂商还比较少，主要原因是储能市场还处在初期，市场对于储能未来的发展还存在很大的疑虑，因此绝大部分厂商都没有进行储能相关BMS的开发。在实际的商业环境中，也有厂家购买电动汽车电池BMS用作储能电池的BMS用，相信在未来专业电动汽车的 BMS 生产厂商也极有可能成为大规模储能项目使用的BMS供应商的重要组成部分。</p>
<p>现阶段，<strong>各个储能系统供应商提供的BMS缺乏统一标准</strong>。不同厂家对BMS的设计、定义都不同，而且根据各家适配电池的不同，采用的SOX算法、均衡技术、上传的通信数据内容可能也各不相同。在BMS的实际应用中，这样的差异会增加应用成本，不利于产业发展。因此，以后BMS的标准化、模块化也将是一个重要的发展方向。</p>
<p>全文完</p>
<p>参考：<br>BMS电池管理系统由浅入深全方位解析<br>电池管理系统BMS中SOC算法浅析<br>电池SOC估计浅谈<br>锂电池保护板的主动均衡和被动均衡的区别 - 基准&#x2F;监控&#x2F;保护电路<br>一文读懂动力电池BMS均衡功能<br>电力储能系统用BMS与动力电池系统BMS解析</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-springboot整合mybatis源码分析"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/07/06/springboot%E6%95%B4%E5%90%88mybatis%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"
    >springboot整合mybatis源码分析</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/07/06/springboot%E6%95%B4%E5%90%88mybatis%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/" class="article-date">
  <time datetime="2023-07-06T06:42:18.000Z" itemprop="datePublished">2023-07-06</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="springboot整合mybatis源码分析-转https-www-cnblogs-com-wbo112-p-15025348-html"><a href="#springboot整合mybatis源码分析-转https-www-cnblogs-com-wbo112-p-15025348-html" class="headerlink" title="springboot整合mybatis源码分析- 转https://www.cnblogs.com/wbo112/p/15025348.html"></a>springboot整合mybatis源码分析- 转<a target="_blank" rel="noopener" href="https://www.cnblogs.com/wbo112/p/15025348.html">https://www.cnblogs.com/wbo112/p/15025348.html</a></h1><p>本文主要讲述mybatis在springboot中是如何被加载执行的，由于涉及的内容会比较多，所以这次只会对调用关系及关键代码点进行讲解，为了避免文章太长，读起来昏昏欲睡，一些不影响整体流程的细节就不涉及了。</p>
<p>源码位置<a target="_blank" rel="noopener" href="https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis">https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis</a></p>
<h3 id="1、预备知识"><a href="#1、预备知识" class="headerlink" title="1、预备知识"></a>1、预备知识</h3><ul>
<li><p>FactoryBean</p>
<p>什么是FactoryBean？</p>
<p>我们先看看FactoryBean的源码</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//由 BeanFactory 中使用的对象实现的接口，这些对象本身是单个对象的工厂。如果一个 bean 实现了这个接口，它就被用作一个对象暴露的工厂，而不是直接作为一个将暴露自己的 bean 实例。</span></span><br><span class="line"><span class="comment">//注意：实现此接口的 bean 不能用作普通 bean。 FactoryBean 以 bean 样式定义，但为 bean 引用公开的对象 (getObject()) 始终是它创建的对象。</span></span><br><span class="line"><span class="comment">//FactoryBeans 可以支持单例和原型，并且可以根据需要懒惰地或在启动时急切地创建对象。 SmartFactoryBean 接口允许公开更细粒度的行为元数据。</span></span><br><span class="line"><span class="comment">//该接口在框架本身中被大量使用，例如用于 AOP org.springframework.aop.framework.ProxyFactoryBean 或 org.springframework.jndi.JndiObjectFactoryBean。它也可以用于自定义组件；然而，这仅适用于基础设施代码。</span></span><br><span class="line"><span class="comment">//FactoryBean 是一个程序化契约。实现不应该依赖于注释驱动的注入或其他反射设施。 getObjectType() getObject() 调用可能会在引导过程的早期到达，甚至在任何后处理器设置之前。如果您需要访问其他 bean，请实现 BeanFactoryAware 并以编程方式获取它们。</span></span><br><span class="line"><span class="comment">//容器只负责管理FactoryBean 实例的生命周期，而不负责管理FactoryBean 创建的对象的生命周期。因此，暴露的 bean 对象（例如 java.io.Closeable.close() 上的 destroy 方法不会被自动调用。相反，FactoryBean 应该实现 DisposableBean 并将任何此类关闭调用委托给底层对象。</span></span><br><span class="line"><span class="comment">//最后，FactoryBean 对象参与包含 BeanFactory 的 bean 创建同步。除了 FactoryBean 本身（或类似的）内部的延迟初始化之外，通常不需要内部同步。</span></span><br><span class="line"><span class="keyword">package</span> org.springframework.beans.factory;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.lang.Nullable;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">FactoryBean</span>&lt;T&gt; &#123;</span><br><span class="line"></span><br><span class="line">	<span class="type">String</span> <span class="variable">OBJECT_TYPE_ATTRIBUTE</span> <span class="operator">=</span> <span class="string">&quot;factoryBeanObjectType&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//返回真正的beanFacotry中的bean对象</span></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	T <span class="title function_">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception;</span><br><span class="line">    </span><br><span class="line">	<span class="comment">//返回真正的beanFacotry中的bean对象的类型</span></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	Class&lt;?&gt; getObjectType();</span><br><span class="line">    </span><br><span class="line">	<span class="comment">//是否单例</span></span><br><span class="line">	<span class="keyword">default</span> <span class="type">boolean</span> <span class="title function_">isSingleton</span><span class="params">()</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面就是FactoryBean的源码了，源码中的注释我都删除掉了。类上的中文注释是翻译的源码上的，方法上的注释是我自己加的。简单来说就是时间这个接口的类是作为对象暴漏的工厂，真正调用getObject()才会得到实际的bean对象。</p>
<h3 id="2、springboot集成mybatis"><a href="#2、springboot集成mybatis" class="headerlink" title="2、springboot集成mybatis"></a>2、springboot集成mybatis</h3><ul>
<li><p>之前的文章简单说到springboot启动的时候会读取META-INF\spring.factories文件，把key&#x3D;org.springframework.boot.autoconfigure.EnableAutoConfiguration的字符串作为类名去加载(启动会配合META-INF\spring-autoconfigure-metadata.properties中的内容过滤掉不符合当前场景的)</p>
<p>springboot集成mybatis也是这样实现的。</p>
</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224425807-1892423111.png" alt="img"></p>
<ul>
<li><p>是由谁来上面的文件的呢</p>
<p>我们的main方法上都会有@SpringBootApplication注解</p>
</li>
</ul>
<p><img src="https://cydmacro.gitee.io/myblog/img/368308-20210717224440261-115567715.png" alt="img"></p>
<p>在SpringBootApplication这个上面会有个@EnableAutoConfiguration注解</p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224449523-1318992810.png" alt="img"></p>
<p>在这个上面会有import注解，参数是AutoConfigurationImportSelector.class。真正读取上面文件的类就是AutoConfigurationImportSelector。</p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224457195-1778508685.png" alt="img"></p>
<p>AutoConfigurationImportSelector.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//真正的读取代码是在这里</span></span><br><span class="line">	<span class="keyword">protected</span> AutoConfigurationEntry <span class="title function_">getAutoConfigurationEntry</span><span class="params">(AnnotationMetadata annotationMetadata)</span> &#123;</span><br><span class="line">		<span class="keyword">if</span> (!isEnabled(annotationMetadata)) &#123;</span><br><span class="line">			<span class="keyword">return</span> EMPTY_ENTRY;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="type">AnnotationAttributes</span> <span class="variable">attributes</span> <span class="operator">=</span> getAttributes(annotationMetadata);</span><br><span class="line">		<span class="comment">//在这里读取META-INF\spring.factories文件中key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的值</span></span><br><span class="line">		List&lt;String&gt; configurations = getCandidateConfigurations(annotationMetadata, attributes);</span><br><span class="line">		configurations = removeDuplicates(configurations);</span><br><span class="line">		Set&lt;String&gt; exclusions = getExclusions(annotationMetadata, attributes);</span><br><span class="line">		checkExcludedClasses(configurations, exclusions);</span><br><span class="line">		configurations.removeAll(exclusions);</span><br><span class="line">        <span class="comment">//在这里读取META-INF\spring.factories文件中key=org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的值根据META-INF\spring-autoconfigure-metadata.properties进行过滤</span></span><br><span class="line">		configurations = getConfigurationClassFilter().filter(configurations);</span><br><span class="line">		fireAutoConfigurationImportEvents(configurations, exclusions);</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AutoConfigurationEntry</span>(configurations, exclusions);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>

<p>读取META-INF\spring-autoconfigure-metadata.properties文件是在AutoConfigurationImportSelector的内部类ConfigurationClassFilter的构造方法中，真正的过滤也是在这个内部类中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">		ConfigurationClassFilter(ClassLoader classLoader, List&lt;AutoConfigurationImportFilter&gt; filters) &#123;</span><br><span class="line">		<span class="comment">//在这里读取的META-INF\spring-autoconfigure-metadata.properties</span></span><br><span class="line">			<span class="built_in">this</span>.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);</span><br><span class="line">			<span class="built_in">this</span>.filters = filters;</span><br><span class="line">		&#125;</span><br><span class="line"><span class="comment">//这个也是ConfigurationClassFilter的方法</span></span><br><span class="line">List&lt;String&gt; <span class="title function_">filter</span><span class="params">(List&lt;String&gt; configurations)</span> &#123;</span><br><span class="line">   <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line">   String[] candidates = StringUtils.toStringArray(configurations);</span><br><span class="line">   <span class="type">boolean</span> <span class="variable">skipped</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">   <span class="keyword">for</span> (AutoConfigurationImportFilter filter : <span class="built_in">this</span>.filters) &#123;</span><br><span class="line">   	<span class="comment">//执行过滤</span></span><br><span class="line">      <span class="type">boolean</span>[] match = filter.match(candidates, <span class="built_in">this</span>.autoConfigurationMetadata);</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; match.length; i++) &#123;</span><br><span class="line">         <span class="keyword">if</span> (!match[i]) &#123;</span><br><span class="line">            candidates[i] = <span class="literal">null</span>;</span><br><span class="line">            skipped = <span class="literal">true</span>;</span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">     + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + <span class="string">&quot; ms&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>默认的过滤器是有3个，是在这里</p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224513000-1782254253.png" alt="img"></p>
<p>在读取过程中就会读取mybatis-spring-boot-autoconfigure-2.2.0.jar中的META-INF\spring.factories配置(本文第一个图)，加载下面两个类</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span>=<span class="string">\</span></span><br><span class="line"><span class="string">org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\</span></span><br><span class="line"><span class="string">org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration</span></span><br></pre></td></tr></table></figure>

<p>同样的也会用mybatis-spring-boot-autoconfigure-2.2.0.jar中的META-INF\spring-autoconfigure-metadata.properties文件进行过滤。</p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224524837-1746615215.png" alt="img"></p>
<p>这里的过滤其实就是用类名+.+Conditional*来作为过滤的</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnClass</span>=<span class="string">org.apache.ibatis.session.SqlSessionFactory,org.mybatis.spring.SqlSessionFactoryBean</span></span><br><span class="line"><span class="attr">org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnSingleCandidate</span>=<span class="string">javax.sql.DataSource</span></span><br></pre></td></tr></table></figure>

<p>比如上面两行org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnClass是根据等号后面的类是否存在来判断是否被过滤掉，org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnSingleCandidate看代码也是判断对应类是否存在来判断的，多个条件是and的关系</p>
<p>这两个条件的具体判断代码位置在OnBeanCondition中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,</span><br><span class="line">      AutoConfigurationMetadata autoConfigurationMetadata) &#123;</span><br><span class="line">   ConditionOutcome[] outcomes = <span class="keyword">new</span> <span class="title class_">ConditionOutcome</span>[autoConfigurationClasses.length];</span><br><span class="line">   <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; outcomes.length; i++) &#123;</span><br><span class="line">      <span class="type">String</span> <span class="variable">autoConfigurationClass</span> <span class="operator">=</span> autoConfigurationClasses[i];</span><br><span class="line">      <span class="keyword">if</span> (autoConfigurationClass != <span class="literal">null</span>) &#123;</span><br><span class="line">          <span class="comment">//获取*.ConditionalOnClass等号后面的值</span></span><br><span class="line">         Set&lt;String&gt; onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, <span class="string">&quot;ConditionalOnBean&quot;</span>);</span><br><span class="line">          <span class="comment">//进行判断，返回null就是OK的，条件不存在也是null</span></span><br><span class="line">         outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);</span><br><span class="line">         <span class="keyword">if</span> (outcomes[i] == <span class="literal">null</span>) &#123;</span><br><span class="line">             <span class="comment">//获取*.ConditionalOnSingleCandidate等号后面的值</span></span><br><span class="line">            Set&lt;String&gt; onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,</span><br><span class="line">                  <span class="string">&quot;ConditionalOnSingleCandidate&quot;</span>);</span><br><span class="line">              <span class="comment">//进行判断，返回null就是OK的，条件不存在也是null</span></span><br><span class="line">            outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);</span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> outcomes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当前的场景这两个类都是符合的不会被过滤掉。这两个类就会被加载。</p>
<h3 id="3、MybatisAutoConfiguration的加载，beanFatory加载-Mapper类"><a href="#3、MybatisAutoConfiguration的加载，beanFatory加载-Mapper类" class="headerlink" title="3、MybatisAutoConfiguration的加载，beanFatory加载@Mapper类"></a>3、MybatisAutoConfiguration的加载，beanFatory加载@Mapper类</h3><p>下面具体看下加载的过程，主要是MybatisAutoConfiguration这个类，所以我们这里也就只看这个类了</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//这里就把类上的注解粘了出来简单都介绍下</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//这个注解大家都比较熟悉，不多说了</span></span><br><span class="line"><span class="meta">@org</span>.springframework.context.annotation.Configuration</span><br><span class="line"><span class="comment">//这个还是条件注解，处理的类和上面配置文件中的处理都在同一个类中</span></span><br><span class="line"><span class="comment">//这个是判断对应类是否存在</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(&#123; SqlSessionFactory.class, SqlSessionFactoryBean.class &#125;)</span></span><br><span class="line"><span class="comment">//这个就和配置文件中的处理有区别了，这个是判断beanFacotry中是否只有一个类型DataSource.class的bean的定义，或者有多个，但有一个主要的</span></span><br><span class="line"><span class="meta">@ConditionalOnSingleCandidate(DataSource.class)</span></span><br><span class="line"><span class="comment">//这个是去让注入配置文件</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(MybatisProperties.class)</span></span><br><span class="line"><span class="comment">//这个是排序的</span></span><br><span class="line"><span class="meta">@AutoConfigureAfter(&#123; DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class &#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MybatisAutoConfiguration</span> <span class="keyword">implements</span> <span class="title class_">InitializingBean</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面这个图是ConditionalOnClass，ConditionalOnSingleCandidate执行处理的位置，还是在OnBeanCondition这个类中</p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224542641-993599938.png" alt="img"></p>
<p>在进行完过滤判断后，确定MybatisAutoConfiguration类要加载之后，会扫描内部类和方法上，符合条件的也都会被加载，主要是找@Configuration，@Bean这两个注解。我们当前这个类中依次会加载如下内容</p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224555891-677971314.png" alt="img"></p>
<p>加载这个内部类，ConditionalOnMissingBean这个条件当前是成立的，关于条件这块都会忽略掉，不多说这块了。同时由于类上有Import注解，也就会继续加载AutoConfiguredMapperScannerRegistrar.class这个类，</p>
<p>类上有方法@Bean注解</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/368308-20210717224612256-1821173732.png" alt="img"></p>
<p><img src="https://img2020.cnblogs.com/blog/368308/202107/368308-20210717224621146-1713573461.png" alt="img"></p>
<p>这两个类也会被加载</p>
<p>这里就会加载MybatisAutoConfiguration，MapperScannerRegistrarNotFoundConfiguration，AutoConfiguredMapperScannerRegistrar，SqlSessionTemplate，SqlSessionFactory这几个作为bean的定义(后面两个是方法)。</p>
<p>由于AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口，在加载的过程中，会调用registerBeanDefinitions去注册额外的bean的定义。</p>
<p>这个方法比较重要，我们进去看看</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">   <span class="meta">@Override</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerBeanDefinitions</span><span class="params">(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)</span> &#123;</span><br><span class="line"><span class="comment">//这个是判断beanFactory中是否存在AutoConfigurationPackages的bean，这里是存在的</span></span><br><span class="line">     <span class="keyword">if</span> (!AutoConfigurationPackages.has(<span class="built_in">this</span>.beanFactory)) &#123;</span><br><span class="line">       logger.debug(<span class="string">&quot;Could not determine auto-configuration package, automatic mapper scanning disabled.&quot;</span>);</span><br><span class="line">       <span class="keyword">return</span>;</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     logger.debug(<span class="string">&quot;Searching for mappers annotated with @Mapper&quot;</span>);</span><br><span class="line">  <span class="comment">//这里获取要扫描的包名，这里会是&#123;“com.example.springbootmybatis”&#125;，其实也就是我们在哪里找mapper,后面单独说下这个</span></span><br><span class="line">     List&lt;String&gt; packages = AutoConfigurationPackages.get(<span class="built_in">this</span>.beanFactory);</span><br><span class="line">     <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">       packages.forEach(pkg -&gt; logger.debug(<span class="string">&quot;Using auto-configuration base package &#x27;&#123;&#125;&#x27;&quot;</span>, pkg));</span><br><span class="line">     &#125;</span><br><span class="line"> <span class="comment">//下面这些代码主要就是定义一个bean的定义，添加到BeanFactory中</span></span><br><span class="line">     <span class="type">BeanDefinitionBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);</span><br><span class="line">     builder.addPropertyValue(<span class="string">&quot;processPropertyPlaceHolders&quot;</span>, <span class="literal">true</span>);</span><br><span class="line">     <span class="comment">//这就是要扫描的注解类型，就是@Mapper</span></span><br><span class="line">     builder.addPropertyValue(<span class="string">&quot;annotationClass&quot;</span>, Mapper.class);</span><br><span class="line">     <span class="comment">//这里是要扫描的包的路径</span></span><br><span class="line">     builder.addPropertyValue(<span class="string">&quot;basePackage&quot;</span>, StringUtils.collectionToCommaDelimitedString(packages));</span><br><span class="line">     <span class="type">BeanWrapper</span> <span class="variable">beanWrapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BeanWrapperImpl</span>(MapperScannerConfigurer.class);</span><br><span class="line">     Set&lt;String&gt; propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)</span><br><span class="line">         .collect(Collectors.toSet());</span><br><span class="line">     <span class="keyword">if</span> (propertyNames.contains(<span class="string">&quot;lazyInitialization&quot;</span>)) &#123;</span><br><span class="line">       <span class="comment">// Need to mybatis-spring 2.0.2+</span></span><br><span class="line">       builder.addPropertyValue(<span class="string">&quot;lazyInitialization&quot;</span>, <span class="string">&quot;$&#123;mybatis.lazy-initialization:false&#125;&quot;</span>);</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="keyword">if</span> (propertyNames.contains(<span class="string">&quot;defaultScope&quot;</span>)) &#123;</span><br><span class="line">       <span class="comment">// Need to mybatis-spring 2.0.6+</span></span><br><span class="line">       builder.addPropertyValue(<span class="string">&quot;defaultScope&quot;</span>, <span class="string">&quot;$&#123;mybatis.mapper-default-scope:&#125;&quot;</span>);</span><br><span class="line">     &#125;</span><br><span class="line">     registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<hr>
<p>这里我们来看下AutoConfigurationPackages.get(this.beanFactory)这个获取的包名是如何的</p>
<p>先看下这个方法的里面调用</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;String&gt; <span class="title function_">get</span><span class="params">(BeanFactory beanFactory)</span> &#123;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">           <span class="comment">//BEAN = AutoConfigurationPackages.class.getName(),这个方法也就是获取bean的名字是AutoConfigurationPackages.class.getName(),AutoConfigurationPackages.BasePackages.class类型的bean，再调用AutoConfigurationPackages.BasePackages的get方法</span></span><br><span class="line">           <span class="comment">//下面我们分析下这个值是怎么来的</span></span><br><span class="line">		<span class="keyword">return</span> beanFactory.getBean(BEAN, BasePackages.class).get();</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">catch</span> (NoSuchBeanDefinitionException ex) &#123;</span><br><span class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Unable to retrieve @EnableAutoConfiguration base packages&quot;</span>);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由于我们main方法的类上有@SpringBootApplication注解，它的注解上有@EnableAutoConfiguration，它的注解上有@AutoConfigurationPackage，它的注解上@Import(AutoConfigurationPackages.Registrar.class)，在加载我们的主类SpringbootMybatisApplication时，就会调用到AutoConfigurationPackages.Registrar的registerBeanDefinitions这个方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Registrar</span> <span class="keyword">implements</span> <span class="title class_">ImportBeanDefinitionRegistrar</span>, DeterminableImports &#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">       <span class="comment">//这里的metadata就是我们的SpringbootMybatisApplication</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerBeanDefinitions</span><span class="params">(AnnotationMetadata metadata, BeanDefinitionRegistry registry)</span> &#123;</span><br><span class="line">          	<span class="comment">//我们先看看new PackageImports(metadata)这个方法</span></span><br><span class="line">		register(registry, <span class="keyword">new</span> <span class="title class_">PackageImports</span>(metadata).getPackageNames().toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">	&#125;</span><br><span class="line">......</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">	<span class="comment">//这个比较简单了，就是获取一些包名</span></span><br><span class="line">	PackageImports(AnnotationMetadata metadata) &#123;</span><br><span class="line">           </span><br><span class="line">		<span class="type">AnnotationAttributes</span> <span class="variable">attributes</span> <span class="operator">=</span> AnnotationAttributes</span><br><span class="line">				.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), <span class="literal">false</span>));</span><br><span class="line">           <span class="comment">//这里是获取我们主类SpringbootMybatisApplication上注解的&quot;basePackages&quot;属性，由于我们没有配置，所以这里就是null</span></span><br><span class="line">		List&lt;String&gt; packageNames = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(Arrays.asList(attributes.getStringArray(<span class="string">&quot;basePackages&quot;</span>)));</span><br><span class="line">             <span class="comment">//这里是获取我们主类SpringbootMybatisApplication上注解的&quot;basePackageClasses&quot;属性，由于我们没有配置，所以也不会走到这个for循环</span></span><br><span class="line">		<span class="keyword">for</span> (Class&lt;?&gt; basePackageClass : attributes.getClassArray(<span class="string">&quot;basePackageClasses&quot;</span>)) &#123;</span><br><span class="line">			packageNames.add(basePackageClass.getPackage().getName());</span><br><span class="line">		&#125;</span><br><span class="line">           <span class="comment">//这里的packageNames就是空的，会走到这个if分支</span></span><br><span class="line">		<span class="keyword">if</span> (packageNames.isEmpty()) &#123;</span><br><span class="line">               <span class="comment">//packageNames增加当前SpringbootMybatisApplication类所在的包名com.example.springbootmybatis</span></span><br><span class="line">			packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));</span><br><span class="line">		&#125;</span><br><span class="line">           <span class="comment">//这里的this.packageNames中就只会有com.example.springbootmybatis</span></span><br><span class="line">		<span class="built_in">this</span>.packageNames = Collections.unmodifiableList(packageNames);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>

<p>我们再回头看上面的registerBeanDefinitions</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Registrar</span> <span class="keyword">implements</span> <span class="title class_">ImportBeanDefinitionRegistrar</span>, DeterminableImports &#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerBeanDefinitions</span><span class="params">(AnnotationMetadata metadata, BeanDefinitionRegistry registry)</span> &#123;</span><br><span class="line">       	<span class="comment">//上面new PackageImports(metadata)已经分析过了，这时new PackageImports(metadata).getPackageNames().toArray(new String[0])就是&#123;“com.example.springbootmybatis”&#125;</span></span><br><span class="line">        <span class="comment">//这个方法就不点进去了，在这里简单说说</span></span><br><span class="line">        <span class="comment">//方法就是在registry(也就是beanFatory)中增加一个bean的定义(BasePackagesBeanDefinition，它的参数就是&#123;“com.example.springbootmybatis”&#125;),所以上面的AutoConfigurationPackages.get(this.beanFactory)这句返回的结果就是&#123;“com.example.springbootmybatis”&#125;</span></span><br><span class="line">		register(registry, <span class="keyword">new</span> <span class="title class_">PackageImports</span>(metadata).getPackageNames().toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">	&#125;</span><br><span class="line">......</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<p>我们继续看 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) 中在registry(也就是beanFacotry)中增加的bean的定义registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition())</p>
<p>由于MapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor,所以它就会被生成bean之前加载，调用它的postProcessBeanDefinitionRegistry方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postProcessBeanDefinitionRegistry</span><span class="params">(BeanDefinitionRegistry registry)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.processPropertyPlaceHolders) &#123;</span><br><span class="line">      <span class="comment">//这个是主要设置一些属性，比如上面包名，要扫描的注解类名称等等</span></span><br><span class="line">      processPropertyPlaceHolders();</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//这个类看名字，大家都知道是干什么的了。主要就是扫描mapper注解的类</span></span><br><span class="line">    <span class="type">ClassPathMapperScanner</span> <span class="variable">scanner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathMapperScanner</span>(registry);</span><br><span class="line">    scanner.setAddToConfig(<span class="built_in">this</span>.addToConfig);</span><br><span class="line">    scanner.setAnnotationClass(<span class="built_in">this</span>.annotationClass);</span><br><span class="line">    scanner.setMarkerInterface(<span class="built_in">this</span>.markerInterface);</span><br><span class="line">    scanner.setSqlSessionFactory(<span class="built_in">this</span>.sqlSessionFactory);</span><br><span class="line">    scanner.setSqlSessionTemplate(<span class="built_in">this</span>.sqlSessionTemplate);</span><br><span class="line">    scanner.setSqlSessionFactoryBeanName(<span class="built_in">this</span>.sqlSessionFactoryBeanName);</span><br><span class="line">    scanner.setSqlSessionTemplateBeanName(<span class="built_in">this</span>.sqlSessionTemplateBeanName);</span><br><span class="line">    scanner.setResourceLoader(<span class="built_in">this</span>.applicationContext);</span><br><span class="line">    scanner.setBeanNameGenerator(<span class="built_in">this</span>.nameGenerator);</span><br><span class="line">    scanner.setMapperFactoryBeanClass(<span class="built_in">this</span>.mapperFactoryBeanClass);</span><br><span class="line">    <span class="keyword">if</span> (StringUtils.hasText(lazyInitialization)) &#123;</span><br><span class="line">      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (StringUtils.hasText(defaultScope)) &#123;</span><br><span class="line">      scanner.setDefaultScope(defaultScope);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//这里是设置要扫描的注解类，这里会设置@Mapper</span></span><br><span class="line">    scanner.registerFilters();</span><br><span class="line">    <span class="comment">//这里就是要根据传入的包名去做扫描了，这里的this.basePackage就是上面说的com.example.springbootmybatis</span></span><br><span class="line">    scanner.scan(</span><br><span class="line">        StringUtils.tokenizeToStringArray(<span class="built_in">this</span>.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));</span><br><span class="line">  &#125;</span><br><span class="line">	<span class="keyword">public</span> <span class="type">int</span> <span class="title function_">scan</span><span class="params">(String... basePackages)</span> &#123;</span><br><span class="line">        ......</span><br><span class="line">		<span class="comment">//在这里进行mapper的扫描</span></span><br><span class="line">		doScan(basePackages);</span><br><span class="line">		......</span><br><span class="line">	&#125;</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Set&lt;BeanDefinitionHolder&gt; <span class="title function_">doScan</span><span class="params">(String... basePackages)</span> &#123;</span><br><span class="line">    <span class="comment">//首先会进入这里，我们进去看看  </span></span><br><span class="line">  Set&lt;BeanDefinitionHolder&gt; beanDefinitions = <span class="built_in">super</span>.doScan(basePackages);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (beanDefinitions.isEmpty()) &#123;</span><br><span class="line">    LOGGER.warn(() -&gt; <span class="string">&quot;No MyBatis mapper was found in &#x27;&quot;</span> + Arrays.toString(basePackages)</span><br><span class="line">        + <span class="string">&quot;&#x27; package. Please check your configuration.&quot;</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">     <span class="comment">//我们会走到这里，这个方法也比较重要，我们进去看看</span></span><br><span class="line">    processBeanDefinitions(beanDefinitions);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> beanDefinitions;</span><br><span class="line">&#125;</span><br><span class="line">	<span class="keyword">protected</span> Set&lt;BeanDefinitionHolder&gt; <span class="title function_">doScan</span><span class="params">(String... basePackages)</span> &#123;</span><br><span class="line">		Assert.notEmpty(basePackages, <span class="string">&quot;At least one base package must be specified&quot;</span>);</span><br><span class="line">		Set&lt;BeanDefinitionHolder&gt; beanDefinitions = <span class="keyword">new</span> <span class="title class_">LinkedHashSet</span>&lt;&gt;();</span><br><span class="line">        <span class="comment">//根据传入的包名遍历</span></span><br><span class="line">		<span class="keyword">for</span> (String basePackage : basePackages) &#123;</span><br><span class="line">            <span class="comment">//这里就是扫描类路径下的mapper注解类了。</span></span><br><span class="line">            <span class="comment">//比如我这里的传入的包名是com.example.springbootmybatis,就会被转换成classpath*:com/example/springbootmybatis/**/*.class这个路径进行解析查找，将找到的类作为BeanDefinition的定义，返回</span></span><br><span class="line">			Set&lt;BeanDefinition&gt; candidates = findCandidateComponents(basePackage);</span><br><span class="line">			<span class="keyword">for</span> (BeanDefinition candidate : candidates) &#123;</span><br><span class="line">				<span class="type">ScopeMetadata</span> <span class="variable">scopeMetadata</span> <span class="operator">=</span> <span class="built_in">this</span>.scopeMetadataResolver.resolveScopeMetadata(candidate);</span><br><span class="line">				candidate.setScope(scopeMetadata.getScopeName());</span><br><span class="line">                <span class="comment">//获取bean的名字</span></span><br><span class="line">				<span class="type">String</span> <span class="variable">beanName</span> <span class="operator">=</span> <span class="built_in">this</span>.beanNameGenerator.generateBeanName(candidate, <span class="built_in">this</span>.registry);</span><br><span class="line">                <span class="comment">//这里candidate的类型是ScannedGenericBeanDefinition,所以会进入这个if分支，这个没啥，就是设置一些bean初始化相关属性，不关注了</span></span><br><span class="line">				<span class="keyword">if</span> (candidate <span class="keyword">instanceof</span> AbstractBeanDefinition) &#123;</span><br><span class="line">					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);</span><br><span class="line">				&#125;</span><br><span class="line">                <span class="comment">//也会进入这个if分支，这个也不进去看了</span></span><br><span class="line">				<span class="keyword">if</span> (candidate <span class="keyword">instanceof</span> AnnotatedBeanDefinition) &#123;</span><br><span class="line">					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);</span><br><span class="line">				&#125;</span><br><span class="line">                <span class="comment">//这个是判断beanFactory是否包含beanName的bean的定义，不包含就会进入分支，这个分支也没啥特殊的，就是把bean的定义添加到beanFactory中</span></span><br><span class="line">				<span class="keyword">if</span> (checkCandidate(beanName, candidate)) &#123;</span><br><span class="line">					<span class="type">BeanDefinitionHolder</span> <span class="variable">definitionHolder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BeanDefinitionHolder</span>(candidate, beanName);</span><br><span class="line">					definitionHolder =</span><br><span class="line">							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, <span class="built_in">this</span>.registry);</span><br><span class="line">					beanDefinitions.add(definitionHolder);</span><br><span class="line">					registerBeanDefinition(definitionHolder, <span class="built_in">this</span>.registry);</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> beanDefinitions;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>

<p>继续会进入这个方法,这个方法比较长，不过比较重要，大家一起跟我看吧，非关键代码我都省略掉吧</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processBeanDefinitions</span><span class="params">(Set&lt;BeanDefinitionHolder&gt; beanDefinitions)</span> &#123;</span><br><span class="line">	......</span><br><span class="line">	<span class="comment">//这里是给bean定义的添加一个构造方法参数，就是我们扫描出来mapper注解类的类名，我这里是com.example.springbootmybatis.mapper.UserMapper。这个是为后续选择哪个构造方法服务的</span></span><br><span class="line">    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); <span class="comment">// issue #59</span></span><br><span class="line">    <span class="comment">//这个就是设置对应bean的类的类，这里设置成了org.mybatis.spring.mapper.MapperFactoryBean这个类，这注意这个类实现了FactoryBean接口</span></span><br><span class="line">    definition.setBeanClass(<span class="built_in">this</span>.mapperFactoryBeanClass);</span><br><span class="line">	......</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!explicitFactoryUsed) &#123;</span><br><span class="line">      LOGGER.debug(() -&gt; <span class="string">&quot;Enabling autowire by type for MapperFactoryBean with name &#x27;&quot;</span> + holder.getBeanName() + <span class="string">&quot;&#x27;.&quot;</span>);</span><br><span class="line">       <span class="comment">//这句也比较重要，代表属性注入模式</span></span><br><span class="line">      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);</span><br><span class="line">    &#125;</span><br><span class="line">	......</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到这里，对于@Mapper类的加载就完成了，后面的都是在生成对应bean的时候完成的</p>
<h3 id="4-beanFatory生成对应-Mapper类的bean对象"><a href="#4-beanFatory生成对应-Mapper类的bean对象" class="headerlink" title="4.beanFatory生成对应@Mapper类的bean对象"></a>4.beanFatory生成对应@Mapper类的bean对象</h3><p>创建bean对象实例会调用到AbstractBeanFactory的doGetBean这个方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> &lt;T&gt; T <span class="title function_">doGetBean</span><span class="params">(</span></span><br><span class="line"><span class="params">			String name, <span class="meta">@Nullable</span> Class&lt;T&gt; requiredType, <span class="meta">@Nullable</span> Object[] args, <span class="type">boolean</span> typeCheckOnly)</span></span><br><span class="line">			<span class="keyword">throws</span> BeansException &#123;</span><br><span class="line">				......</span><br><span class="line">				<span class="comment">// Create bean instance.</span></span><br><span class="line">                <span class="comment">//由于我们的是单例对象，会走到这个分支</span></span><br><span class="line">				<span class="keyword">if</span> (mbd.isSingleton()) &#123;</span><br><span class="line">					sharedInstance = getSingleton(beanName, () -&gt; &#123;</span><br><span class="line">						<span class="keyword">try</span> &#123;</span><br><span class="line">                            <span class="comment">//在这个方法中会创建bean对象，我们下面看看这个方法</span></span><br><span class="line">							<span class="keyword">return</span> createBean(beanName, mbd, args);</span><br><span class="line">						&#125;</span><br><span class="line">						<span class="keyword">catch</span> (BeansException ex) &#123;</span><br><span class="line">							<span class="comment">// Explicitly remove instance from singleton cache: It might have been put there</span></span><br><span class="line">							<span class="comment">// eagerly by the creation process, to allow for circular reference resolution.</span></span><br><span class="line">							<span class="comment">// Also remove any beans that received a temporary reference to the bean.</span></span><br><span class="line">							destroySingleton(beanName);</span><br><span class="line">							<span class="keyword">throw</span> ex;</span><br><span class="line">						&#125;</span><br><span class="line">					&#125;);</span><br><span class="line">                    <span class="comment">//由于我们的sharedInstance对象是，所以在这里最终会调用到FactoryBeanRegistrySupport的doGetObjectFromFactoryBean方法，返回真正的userMapper的bean对象，也就是调用MapperFactoryBean的getObject()方法</span></span><br><span class="line">					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);</span><br><span class="line">				&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>

<p>创建bean对象实例最终都会走到AbstractAutowireCapableBeanFactory类的doCreateBean这个方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">	<span class="comment">//我们来分析下这块代码，不相关的代码我都省略掉</span></span><br><span class="line"></span><br><span class="line">		<span class="keyword">protected</span> Object <span class="title function_">doCreateBean</span><span class="params">(String beanName, RootBeanDefinition mbd, <span class="meta">@Nullable</span> Object[] args)</span></span><br><span class="line">			<span class="keyword">throws</span> BeanCreationException &#123;</span><br><span class="line"></span><br><span class="line">		<span class="comment">// Instantiate the bean.</span></span><br><span class="line">		<span class="type">BeanWrapper</span> <span class="variable">instanceWrapper</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">		<span class="keyword">if</span> (mbd.isSingleton()) &#123;</span><br><span class="line">            <span class="comment">//看this.factoryBeanInstanceCache这个名字就知道是factoryBean实例的缓存，其实我们当前的userMapper创建的实例已经缓存到这里了，不过无所谓，就算之前没有创建缓存到这里，下面12行就会去创建。所以我们这里就认为之前没有创建过，去看看13行的代码具体是如何创建userMapper的factoryBean实例的</span></span><br><span class="line">			instanceWrapper = <span class="built_in">this</span>.factoryBeanInstanceCache.remove(beanName);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> (instanceWrapper == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//正常创建userMapper的factoryBean对象是走不到这里的，是在这之前创建的，不过创建方法也是调用的这个</span></span><br><span class="line">            <span class="comment">//这个方法的所用就是根据RootBeanDefinition的getBeanClass()找到对应的类，再查找所有构造方法，根据 RootBeanDefinition.getConstructorArgumentValues()构造方法的参数选择合适的构造方法创建类对象，返回BeanWrapperImpl包装的对象</span></span><br><span class="line">            <span class="comment">//上面的processBeanDefinitions的方法中对RootBeanDefinition的beanClass和constructorArgumentValues都做过了专门的设置。</span></span><br><span class="line">            <span class="comment">//所以我们这里其实是调用的MapperFactoryBean(Class&lt;T&gt; mapperInterface)这个构造方法，里面的参数mapperInterface就是我们的mapper类com.example.springbootmybatis.mapper.UserMapper</span></span><br><span class="line">			instanceWrapper = createBeanInstance(beanName, mbd, args);</span><br><span class="line">		&#125;</span><br><span class="line">        <span class="comment">//这个就是获取创建出来的MapperFactoryBean对象</span></span><br><span class="line">		<span class="type">Object</span> <span class="variable">bean</span> <span class="operator">=</span> instanceWrapper.getWrappedInstance();</span><br><span class="line">        <span class="comment">//这个是创建创建出来的对象的类型，也就是org.mybatis.spring.mapper.MapperFactoryBean</span></span><br><span class="line">		Class&lt;?&gt; beanType = instanceWrapper.getWrappedClass();</span><br><span class="line">		<span class="keyword">if</span> (beanType != NullBean.class) &#123;</span><br><span class="line">			mbd.resolvedTargetType = beanType;</span><br><span class="line">		&#125;</span><br><span class="line">		......</span><br><span class="line"></span><br><span class="line">		<span class="comment">// Initialize the bean instance.</span></span><br><span class="line">		<span class="type">Object</span> <span class="variable">exposedObject</span> <span class="operator">=</span> bean;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//这里就是属性填充了，我们去这里看看</span></span><br><span class="line">			populateBean(beanName, mbd, instanceWrapper);</span><br><span class="line">            <span class="comment">//由于我们的MapperFactoryBean继承了SqlSessionDaoSupport,它继承了DaoSupport,它实现了InitializingBean这个接口，所以在这里也会调用到DaoSupport的afterPropertiesSet方法</span></span><br><span class="line">            exposedObject = initializeBean(beanName, exposedObject, mbd);</span><br><span class="line">        &#125;</span><br><span class="line">		<span class="keyword">return</span> exposedObject;</span><br><span class="line">	&#125;</span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">populateBean</span><span class="params">(String beanName, RootBeanDefinition mbd, <span class="meta">@Nullable</span> BeanWrapper bw)</span> &#123;</span><br><span class="line">	   ......</span><br><span class="line">           <span class="comment">//这个是获取RootBeanDefinition属性注入模式，我们的是在上面processBeanDefinitions这个方法中设置过的</span></span><br><span class="line">           <span class="comment">//definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);就是这句</span></span><br><span class="line">    	<span class="type">int</span> <span class="variable">resolvedAutowireMode</span> <span class="operator">=</span> mbd.getResolvedAutowireMode();</span><br><span class="line">		<span class="keyword">if</span> (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) &#123;</span><br><span class="line">			......</span><br><span class="line">			<span class="keyword">if</span> (resolvedAutowireMode == AUTOWIRE_BY_TYPE) &#123;</span><br><span class="line">                <span class="comment">//最终会走到这里，遍历类的所有属性，使用unsatisfiedNonSimpleProperties方法进行过滤，对属性进行注入。我们这里会对sqlSessionFactory，sqlSessionTemplate这两个属性进行注入(这两个注入的属性都是在MybatisAutoConfiguration的类中，通过方法定义的bean对象，上面也说过了)</span></span><br><span class="line">				autowireByType(beanName, mbd, bw, newPvs);</span><br><span class="line">			&#125;</span><br><span class="line">			pvs = newPvs;</span><br><span class="line">		&#125;</span><br><span class="line">		......</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>

<p>DaoSupport的afterPropertiesSet方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> IllegalArgumentException, BeanInitializationException &#123;</span><br><span class="line">	<span class="comment">// Let abstract subclasses check their configuration.</span></span><br><span class="line">       <span class="comment">//这个方法是由子类MapperFactoryBean实现的，我们进去看看</span></span><br><span class="line">	checkDaoConfig();</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Let concrete implementations initialize themselves.</span></span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		initDao();</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BeanInitializationException</span>(<span class="string">&quot;Initialization of DAO failed&quot;</span>, ex);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>MapperFactoryBean的checkDaoConfig方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">checkDaoConfig</span><span class="params">()</span> &#123;</span><br><span class="line">......</span><br><span class="line"><span class="comment">//这个是获取之前注入的SqlSessionTemplate的Configuration</span></span><br><span class="line">   <span class="type">Configuration</span> <span class="variable">configuration</span> <span class="operator">=</span> getSqlSession().getConfiguration();</span><br><span class="line">   <span class="keyword">if</span> (<span class="built_in">this</span>.addToConfig &amp;&amp; !configuration.hasMapper(<span class="built_in">this</span>.mapperInterface)) &#123;</span><br><span class="line">     <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//会在这里添加我们的mapper类，这里的this.mapperInterface就是com.example.springbootmybatis.mapper.UserMapper，我们进到这里去看看</span></span><br><span class="line">       configuration.addMapper(<span class="built_in">this</span>.mapperInterface);</span><br><span class="line">     &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">       logger.error(<span class="string">&quot;Error while adding the mapper &#x27;&quot;</span> + <span class="built_in">this</span>.mapperInterface + <span class="string">&quot;&#x27; to configuration.&quot;</span>, e);</span><br><span class="line">       <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(e);</span><br><span class="line">     &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">       ErrorContext.instance().reset();</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>Configuration的addMapper方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getMapper</span><span class="params">(Class&lt;T&gt; type, SqlSession sqlSession)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> mapperRegistry.getMapper(type, sqlSession);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>MapperRegistry的addMapper方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">addMapper</span><span class="params">(Class&lt;T&gt; type)</span> &#123;</span><br><span class="line">......</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="comment">//在knownMappers中添加一个key=com.example.springbootmybatis.mapper.UserMapper的MapperProxyFactory对象</span></span><br><span class="line">      knownMappers.put(type, <span class="keyword">new</span> <span class="title class_">MapperProxyFactory</span>&lt;&gt;(type));</span><br><span class="line">      <span class="comment">// It&#x27;s important that the type is added before the parser is run</span></span><br><span class="line">      <span class="comment">// otherwise the binding may automatically be attempted by the</span></span><br><span class="line">      <span class="comment">// mapper parser. If the type is already known, it won&#x27;t try.</span></span><br><span class="line">      <span class="comment">//在这里就是去查找mapper.xml文件了，同样的如果我们不是通过xml配置的sql，而是用注解的方式实现的，具体的查找都是通过下面的parse方法来实现，我们进去parse方法看看</span></span><br><span class="line">      <span class="type">MapperAnnotationBuilder</span> <span class="variable">parser</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MapperAnnotationBuilder</span>(config, type);</span><br><span class="line">      parser.parse();</span><br><span class="line">      loadCompleted = <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (!loadCompleted) &#123;</span><br><span class="line">        knownMappers.remove(type);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>MapperAnnotationBuilder的parse方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">parse</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="type">String</span> <span class="variable">resource</span> <span class="operator">=</span> type.toString();</span><br><span class="line">  <span class="keyword">if</span> (!configuration.isResourceLoaded(resource)) &#123;</span><br><span class="line">    <span class="comment">//这行就是去从类路径加载mapper的xml文件了，具体的路径规则是这样的type.getName().replace(&#x27;.&#x27;, &#x27;/&#x27;) + &quot;.xml&quot;。所以如果我们的mapper的xml文件是按照这种规则指定的，就不需要单独通过mybatis.mapper-locations去单独指定mapper.xml的路径了</span></span><br><span class="line">    loadXmlResource();</span><br><span class="line">    configuration.addLoadedResource(resource);</span><br><span class="line">    assistant.setCurrentNamespace(type.getName());</span><br><span class="line">    parseCache();</span><br><span class="line">    parseCacheRef();</span><br><span class="line">    <span class="comment">//下面这块就是去扫描方法上的注解去生成sql配置了，这里就不进去看了</span></span><br><span class="line">    <span class="keyword">for</span> (Method method : type.getMethods()) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!canHaveStatement(method)) &#123;</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (getAnnotationWrapper(method, <span class="literal">false</span>, Select.class, SelectProvider.class).isPresent()</span><br><span class="line">          &amp;&amp; method.getAnnotation(ResultMap.class) == <span class="literal">null</span>) &#123;</span><br><span class="line">        parseResultMap(method);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        parseStatement(method);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (IncompleteElementException e) &#123;</span><br><span class="line">        configuration.addIncompleteMethod(<span class="keyword">new</span> <span class="title class_">MethodResolver</span>(<span class="built_in">this</span>, method));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  parsePendingMethods();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们再回到doGetBean方法看后面的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> &lt;T&gt; T <span class="title function_">doGetBean</span><span class="params">(</span></span><br><span class="line"><span class="params">			String name, <span class="meta">@Nullable</span> Class&lt;T&gt; requiredType, <span class="meta">@Nullable</span> Object[] args, <span class="type">boolean</span> typeCheckOnly)</span></span><br><span class="line">			<span class="keyword">throws</span> BeansException &#123;</span><br><span class="line">					......</span><br><span class="line">                    <span class="comment">//由于我们的sharedInstance对象是，所以在这里最终会调用到FactoryBeanRegistrySupport的doGetObjectFromFactoryBean方法，返回真正的userMapper的bean对象，也就是调用MapperFactoryBean的getObject()方法</span></span><br><span class="line">					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);</span><br><span class="line">				&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>

<p>MapperFactoryBean的getObject方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> T <span class="title function_">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"><span class="comment">//这个getSqlSession()就是我们上面属性注入的。org.mybatis.spring.SqlSessionTemplate的对象，</span></span><br><span class="line">  <span class="keyword">return</span> getSqlSession().getMapper(<span class="built_in">this</span>.mapperInterface);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最终会调用到MapperRegistry的getMapper方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getMapper</span><span class="params">(Class&lt;T&gt; type, SqlSession sqlSession)</span> &#123;</span><br><span class="line">   <span class="comment">// 我们在上面addMapper的方法中讲过knownMappers已经添加了key=com.example.springbootmybatis.mapper.UserMapper的MapperProxyFactory对象，</span></span><br><span class="line">   <span class="keyword">final</span> MapperProxyFactory&lt;T&gt; mapperProxyFactory = (MapperProxyFactory&lt;T&gt;) knownMappers.get(type);</span><br><span class="line">   <span class="keyword">if</span> (mapperProxyFactory == <span class="literal">null</span>) &#123;</span><br><span class="line">     <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BindingException</span>(<span class="string">&quot;Type &quot;</span> + type + <span class="string">&quot; is not known to the MapperRegistry.&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">try</span> &#123;</span><br><span class="line">     <span class="comment">//这里就是调用MapperProxyFactory的newInstance的方法了</span></span><br><span class="line">     <span class="keyword">return</span> mapperProxyFactory.newInstance(sqlSession);</span><br><span class="line">   &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">     <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BindingException</span>(<span class="string">&quot;Error getting mapper instance. Cause: &quot;</span> + e, e);</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>MapperProxyFactory的newInstance方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> T <span class="title function_">newInstance</span><span class="params">(SqlSession sqlSession)</span> &#123;</span><br><span class="line">  <span class="keyword">final</span> MapperProxy&lt;T&gt; mapperProxy = <span class="keyword">new</span> <span class="title class_">MapperProxy</span>&lt;&gt;(sqlSession, mapperInterface, methodCache);</span><br><span class="line">  <span class="keyword">return</span> newInstance(mapperProxy);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">protected</span> T <span class="title function_">newInstance</span><span class="params">(MapperProxy&lt;T&gt; mapperProxy)</span> &#123;</span><br><span class="line">  <span class="comment">//最终调用到这里，创建一个MapperProxy的代理对象，这个也就是真正的创建的bean对象</span></span><br><span class="line">  <span class="keyword">return</span> (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123; mapperInterface &#125;, mapperProxy);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到这里，springboot整合mybatis到创建出来mapper对象，整个流程就到这里了，后面调用mapper的方法其实也就是通过MapperProxy代理来实现的。具体springboot中调用mybatis的执行的流程将在接下来的一篇给大家讲解。</p>
<h3 id="5-关于-MapperScan"><a href="#5-关于-MapperScan" class="headerlink" title="5.关于@MapperScan"></a>5.关于@MapperScan</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@MapperScan(<span class="params">basePackages = <span class="string">&quot;com.example.springbootmybatis&quot;</span>,annotationClass = Mapper.<span class="keyword">class</span></span>)</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>basePackages是指定扫描的包名</p>
<p>annotationClass是查找对应包名下类上有个Mapper注解的接口，如果没有指定这个参数，会将获取到的所有的类都当作Mapper去处理，这时后面执行sql操作就会出错了<strong>不知道是不是mybatis的版本问题，之前记得是不需要指定这个参数的</strong></p>
<p>具体的代码位置是在ClassPathMapperScanner类的registerFilters方法中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerFilters</span><span class="params">()</span> &#123;</span><br><span class="line">   <span class="type">boolean</span> <span class="variable">acceptAllInterfaces</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// if specified, use the given annotation and / or marker interface</span></span><br><span class="line">   <span class="comment">//如果设置了annotationClass = Mapper.class就会走到这里，在这里会过滤class上是否有Mapper注解</span></span><br><span class="line">   <span class="keyword">if</span> (<span class="built_in">this</span>.annotationClass != <span class="literal">null</span>) &#123;</span><br><span class="line">     addIncludeFilter(<span class="keyword">new</span> <span class="title class_">AnnotationTypeFilter</span>(<span class="built_in">this</span>.annotationClass));</span><br><span class="line">     acceptAllInterfaces = <span class="literal">false</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// override AssignableTypeFilter to ignore matches on the actual marker interface</span></span><br><span class="line">   <span class="keyword">if</span> (<span class="built_in">this</span>.markerInterface != <span class="literal">null</span>) &#123;</span><br><span class="line">     addIncludeFilter(<span class="keyword">new</span> <span class="title class_">AssignableTypeFilter</span>(<span class="built_in">this</span>.markerInterface) &#123;</span><br><span class="line">       <span class="meta">@Override</span></span><br><span class="line">       <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">matchClassName</span><span class="params">(String className)</span> &#123;</span><br><span class="line">         <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">       &#125;</span><br><span class="line">     &#125;);</span><br><span class="line">     acceptAllInterfaces = <span class="literal">false</span>;</span><br><span class="line">   &#125;</span><br><span class="line">	</span><br><span class="line">   <span class="comment">//没有设置了annotationClass = Mapper.class，就会走这里，直接返回true</span></span><br><span class="line">   <span class="keyword">if</span> (acceptAllInterfaces) &#123;</span><br><span class="line">     <span class="comment">// default include filter that accepts all classes</span></span><br><span class="line">     addIncludeFilter((metadataReader, metadataReaderFactory) -&gt; <span class="literal">true</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// exclude package-info.java</span></span><br><span class="line">   addExcludeFilter((metadataReader, metadataReaderFactory) -&gt; &#123;</span><br><span class="line">     <span class="type">String</span> <span class="variable">className</span> <span class="operator">=</span> metadataReader.getClassMetadata().getClassName();</span><br><span class="line">     <span class="keyword">return</span> className.endsWith(<span class="string">&quot;package-info&quot;</span>);</span><br><span class="line">   &#125;);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
</blockquote>
<p>大家看到我的demo中是没有使用@MapperScan这个注解的，那什么时候使用这个注解呢，下面我们从源码来看看</p>
<p>MapperScan注解上面会有@Import(MapperScannerRegistrar.class)，@Repeatable(MapperScans.class)这两个注解，MapperScannerRegistrar这个注解实现了ImportBeanDefinitionRegistrar，加载主类的过程中会调用registerBeanDefinitions这个方法，</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerBeanDefinitions</span><span class="params">(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)</span> &#123;</span><br><span class="line">  <span class="comment">//这个是获取主类上MapperScan注解的相关属性比如我们的配置是(@MapperScan(basePackages = &quot;com.example.springbootmybatis&quot;))，比如basePackages属性等等都是这个注解上的</span></span><br><span class="line">  <span class="type">AnnotationAttributes</span> <span class="variable">mapperScanAttrs</span> <span class="operator">=</span> AnnotationAttributes</span><br><span class="line">      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));</span><br><span class="line">  <span class="keyword">if</span> (mapperScanAttrs != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="comment">//这里就会根据这些属性创建一个MapperScannerConfigurer类的bean的定义，添加到beanFatory中</span></span><br><span class="line">    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,</span><br><span class="line">        generateBaseBeanName(importingClassMetadata, <span class="number">0</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们再看看之前说的MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration这个内部类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@org</span>.springframework.context.annotation.Configuration</span><br><span class="line"><span class="meta">@Import(AutoConfiguredMapperScannerRegistrar.class)</span></span><br><span class="line"><span class="comment">//由于我们上面已经在beanFactory中添加了MapperScannerConfigurer这个类型的bean的定义，所以这个条件就不会成立，上面的import注解中导入AutoConfiguredMapperScannerRegistrar类也就不会执行</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean(&#123; MapperFactoryBean.class, MapperScannerConfigurer.class &#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MapperScannerRegistrarNotFoundConfiguration</span> <span class="keyword">implements</span> <span class="title class_">InitializingBean</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> &#123;</span><br><span class="line">    logger.debug(</span><br><span class="line">        <span class="string">&quot;Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以@MapperScan的区别主要就在于MapperScannerConfigurer这个bean定义的生成位置不一样</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-分布式事务基础"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/07/04/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%9F%BA%E7%A1%80/"
    >分布式事务基础（转载https://juejin.cn/post/6844903647197806605）</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/07/04/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%9F%BA%E7%A1%80/" class="article-date">
  <time datetime="2023-07-04T02:40:54.000Z" itemprop="datePublished">2023-07-04</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>不知道你是否遇到过这样的情况，去小卖铺买东西，付了钱，但是店主因为处理了一些其他事，居然忘记你付了钱，又叫你重新付。又或者在网上购物明明已经扣款，但是却告诉我没有发生交易。这一系列情况都是因为没有事务导致的。这说明了事务在生活中的一些重要性。有了事务，你去小卖铺买东西，那就是一手交钱一手交货。有了事务，你去网上购物，扣款即产生订单交易。</p>
<h2 id="事务的具体定义"><a href="#事务的具体定义" class="headerlink" title="事务的具体定义"></a>事务的具体定义</h2><p>事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元，组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交，只要其中任一操作执行失败，都将导致整个事务的回滚。简单地说，事务提供一种“要么什么都不做，要么做全套（All or Nothing）”机制。</p>
<h1 id="数据库本地事务"><a href="#数据库本地事务" class="headerlink" title="数据库本地事务"></a>数据库本地事务</h1><h2 id="ACID"><a href="#ACID" class="headerlink" title="ACID"></a>ACID</h2><p>说到数据库事务就不得不说，数据库事务中的四大特性，ACID:</p>
<ul>
<li>A:原子性(Atomicity)</li>
</ul>
<p>一个事务(transaction)中的所有操作，要么全部完成，要么全部不完成，不会结束在中间某个环节。事务在执行过程中发生错误，会被回滚（Rollback）到事务开始前的状态，就像这个事务从来没有执行过一样。</p>
<p>就像你买东西要么交钱收货一起都执行，要么要是发不出货，就退钱。</p>
<ul>
<li>C:一致性(Consistency)</li>
</ul>
<p>事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成，那么系统中所有变化将正确地应用，系统处于有效状态。如果在事务中出现错误，那么系统中的所有变化将自动地回滚，系统返回到原始状态。</p>
<ul>
<li>I:隔离性(Isolation)</li>
</ul>
<p>指的是在并发环境中，当不同的事务同时操纵相同的数据时，每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时，数据所处的状态要么是另一事务修改它之前的状态，要么是另一事务修改它之后的状态，事务不会查看到中间状态的数据。</p>
<p>打个比方，你买东西这个事情，是不影响其他人的。</p>
<ul>
<li>D:持久性(Durability)</li>
</ul>
<p>指的是只要事务成功结束，它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃，重新启动数据库系统后，数据库还能恢复到事务成功结束时的状态。</p>
<p>打个比方，你买东西的时候需要记录在账本上，即使老板忘记了那也有据可查。</p>
<h2 id="InnoDB实现原理"><a href="#InnoDB实现原理" class="headerlink" title="InnoDB实现原理"></a>InnoDB实现原理</h2><p>InnoDB是mysql的一个存储引擎，大部分人对mysql都比较熟悉，这里简单介绍一下数据库事务实现的一些基本原理，在本地事务中，服务和资源在事务的包裹下可以看做是一体的:</p>
<p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/7/26/164d65a2768cd3d6~tplv-t2oaga2asx-zoom-in-crop-mark:3024:0:0:0.awebp" alt="img"></p>
<p>我们的本地事务由资源管理器进行管理:</p>
<p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/7/26/164d65b4f31b2356~tplv-t2oaga2asx-zoom-in-crop-mark:3024:0:0:0.awebp" alt="img"></p>
<p>而事务的ACID是通过InnoDB日志和锁来保证。事务的隔离性是通过数据库锁的机制实现的，持久性通过redo log（重做日志）来实现，原子性和一致性通过Undo log来实现。UndoLog的原理很简单，为了满足事务的原子性，在操作任何数据之前，首先将数据备份到一个地方（这个存储数据备份的地方称为UndoLog）。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句，系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。 和Undo Log相反，RedoLog记录的是新数据的备份。在事务提交前，只要将RedoLog持久化即可，不需要将数据持久化。当系统崩溃时，虽然数据没有持久化，但是RedoLog已经持久化。系统可以根据RedoLog的内容，将所有数据恢复到最新的状态。 对具体实现过程有兴趣的同学可以去自行搜索扩展。</p>
<h1 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h1><h2 id="什么是分布式事务"><a href="#什么是分布式事务" class="headerlink" title="什么是分布式事务"></a>什么是分布式事务</h2><p>分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说，就是一次大的操作由不同的小操作组成，这些小的操作分布在不同的服务器上，且属于不同的应用，分布式事务需要保证这些小操作要么全部成功，要么全部失败。本质上来说，分布式事务就是为了保证不同数据库的数据一致性。</p>
<h2 id="分布式事务产生的原因"><a href="#分布式事务产生的原因" class="headerlink" title="分布式事务产生的原因"></a>分布式事务产生的原因</h2><p>从上面本地事务来看，我们可以看为两块，一个是service产生多个节点，另一个是resource产生多个节点。</p>
<h3 id="service多个节点"><a href="#service多个节点" class="headerlink" title="service多个节点"></a>service多个节点</h3><p>随着互联网快速发展，微服务，SOA等服务架构模式正在被大规模的使用，举个简单的例子，一个公司之内，用户的资产可能分为好多个部分，比如余额，积分，优惠券等等。在公司内部有可能积分功能由一个微服务团队维护，优惠券又是另外的团队维护</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104452336.png" alt="image-20230704104452336"></p>
<p>这样的话就无法保证积分扣减了之后，优惠券能否扣减成功。</p>
<h3 id="resource多个节点"><a href="#resource多个节点" class="headerlink" title="resource多个节点"></a>resource多个节点</h3><p>同样的，互联网发展得太快了，我们的Mysql一般来说装千万级的数据就得进行分库分表，对于一个支付宝的转账业务来说，你给的朋友转钱，有可能你的数据库是在北京，而你的朋友的钱是存在上海，所以我们依然无法保证他们能同时成功。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104506945.png" alt="image-20230704104506945"></p>
<h2 id="分布式事务的基础"><a href="#分布式事务的基础" class="headerlink" title="分布式事务的基础"></a>分布式事务的基础</h2><p>从上面来看分布式事务是随着互联网高速发展应运而生的，这是一个必然的我们之前说过数据库的ACID四大特性，已经无法满足我们分布式事务，这个时候又有一些新的大佬提出一些新的理论:</p>
<h3 id="CAP"><a href="#CAP" class="headerlink" title="CAP"></a>CAP</h3><p>CAP定理，又被叫作布鲁尔定理。对于设计分布式系统来说(不仅仅是分布式事务)的架构师来说，CAP就是你的入门理论。</p>
<ul>
<li>C (一致性):对某个指定的客户端来说，读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说，如果在某个节点更新了数据，那么在其他节点如果都能读取到这个最新的数据，那么就称为强一致，如果有某个节点没有读取到，那就是分布式不一致。</li>
<li>A (可用性)：非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间，一个是合理的响应。合理的时间指的是请求不能无限被阻塞，应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的，这里的正确指的是比如应该返回50，而不是返回40。</li>
<li>P (分区容错性):当出现网络分区后，系统能够继续工作。打个比方，这里个集群有多台机器，有台机器网络出现了问题，但是这个集群仍然可以正常工作。</li>
</ul>
<p>熟悉CAP的人都知道，三者不能共有，如果感兴趣可以搜索CAP的证明，在分布式系统中，网络无法100%可靠，分区其实是一个必然现象，如果我们选择了CA而放弃了P，那么当发生分区现象时，为了保证一致性，这个时候必须拒绝请求，但是A又不允许，所以分布式系统理论上不可能选择CA架构，只能选择CP或者AP架构。</p>
<p>对于CP来说，放弃可用性，追求一致性和分区容错性，我们的zookeeper其实就是追求的强一致。</p>
<p>对于AP来说，放弃一致性(这里说的一致性是强一致性)，追求分区容错性和可用性，这是很多分布式系统设计时的选择，后面的BASE也是根据AP来扩展。</p>
<p>顺便一提，CAP理论中是忽略网络延迟，也就是当事务提交时，从节点A复制到节点B，但是在现实中这个是明显不可能的，所以总会有一定的时间是不一致。同时CAP中选择两个，比如你选择了CP，并不是叫你放弃A。因为P出现的概率实在是太小了，大部分的时间你仍然需要保证CA。就算分区出现了你也要为后来的A做准备，比如通过一些日志的手段，是其他机器回复至可用。</p>
<h3 id="BASE"><a href="#BASE" class="headerlink" title="BASE"></a>BASE</h3><p>BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展</p>
<ol>
<li>基本可用:分布式系统在出现故障时，允许损失部分可用功能，保证核心功能可用。</li>
<li>软状态:允许系统中存在中间状态，这个状态不影响系统可用性，这里指的是CAP中的不一致。</li>
<li>最终一致:最终一致是指经过一段时间后，所有节点数据都将会达到一致。</li>
</ol>
<p>BASE解决了CAP中理论没有网络延迟，在BASE中用软状态和最终一致，保证了延迟后的一致性。BASE和 ACID 是相反的，它完全不同于ACID的强一致性模型，而是通过牺牲强一致性来获得可用性，并允许数据在一段时间内是不一致的，但最终达到一致状态。</p>
<h1 id="分布式事务解决方案"><a href="#分布式事务解决方案" class="headerlink" title="分布式事务解决方案"></a>分布式事务解决方案</h1><p>有了上面的理论基础后，这里介绍开始介绍几种常见的分布式事务的解决方案。</p>
<h2 id="是否真的要分布式事务"><a href="#是否真的要分布式事务" class="headerlink" title="是否真的要分布式事务"></a>是否真的要分布式事务</h2><p>在说方案之前，首先你一定要明确你是否真的需要分布式事务？</p>
<p>上面说过出现分布式事务的两个原因，其中有个原因是因为微服务过多。我见过太多团队一个人维护几个微服务，太多团队过度设计，搞得所有人疲劳不堪，而微服务过多就会引出分布式事务，这个时候我不会建议你去采用下面任何一种方案，而是请把需要事务的微服务聚合成一个单机服务，使用数据库的本地事务。因为不论任何一种方案都会增加你系统的复杂度，这样的成本实在是太高了，千万不要因为追求某些设计，而引入不必要的成本和复杂度。</p>
<p>如果你确定需要引入分布式事务可以看看下面几种常见的方案。</p>
<h2 id="2PC"><a href="#2PC" class="headerlink" title="2PC"></a>2PC</h2><p>说到2PC就不得不聊数据库分布式事务中的 XA Transactions。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104549996.png" alt="image-20230704104549996"></p>
<p>在XA协议中分为两阶段:</p>
<p>第一阶段：事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作，并反映是否可以提交.</p>
<p>第二阶段：事务协调器要求每个数据库提交数据，或者回滚数据。</p>
<p>优点： 尽量保证了数据的强一致，实现成本较低，在各大主流数据库都有自己实现，对于MySQL是从5.5开始支持。</p>
<p>缺点:</p>
<ul>
<li>单点问题:事务管理器在整个流程中扮演的角色很关键，如果其宕机，比如在第一阶段已经完成，在第二阶段正准备提交的时候事务管理器宕机，资源管理器就会一直阻塞，导致数据库无法使用。</li>
<li>同步阻塞:在准备就绪之后，资源管理器中的资源一直处于阻塞，直到提交完成，释放资源。</li>
<li>数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计，但仍然存在数据不一致性的可能，比如在第二阶段中，假设协调者发出了事务commit的通知，但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作，其余的参与者则因为没有收到通知一直处于阻塞状态，这时候就产生了数据的不一致性。</li>
</ul>
<p>总的来说，XA协议比较简单，成本较低，但是其单点问题，以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。</p>
<h2 id="TCC"><a href="#TCC" class="headerlink" title="TCC"></a>TCC</h2><p>关于TCC（Try-Confirm-Cancel）的概念，最早是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。 TCC事务机制相比于上面介绍的XA，解决了其几个缺点: 1.解决了协调者单点，由主业务方发起并完成这个业务活动。业务活动管理器也变成多点，引入集群。 2.同步阻塞:引入超时，超时后进行补偿，并且不会锁定整个资源，将资源转换为业务逻辑形式，粒度变小。 3.数据一致性，有了补偿机制之后，由业务活动管理器控制一致性</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104619731.png" alt="image-20230704104619731"></p>
<p>对于TCC的解释:</p>
<ul>
<li>Try阶段：尝试执行,完成所有业务检查（一致性）,预留必须业务资源（准隔离性）</li>
<li>Confirm阶段：确认执行真正执行业务，不作任何业务检查，只使用Try阶段预留的业务资源，Confirm操作满足幂等性。要求具备幂等设计，Confirm失败后需要进行重试。</li>
<li>Cancel阶段：取消执行，释放Try阶段预留的业务资源 Cancel操作满足幂等性Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。</li>
</ul>
<p>举个简单的例子如果你用100元买了一瓶水， Try阶段:你需要向你的钱包检查是否够100元并锁住这100元，水也是一样的。</p>
<p>如果有一个失败，则进行cancel(释放这100元和这一瓶水)，如果cancel失败不论什么失败都进行重试cancel，所以需要保持幂等。</p>
<p>如果都成功，则进行confirm,确认这100元扣，和这一瓶水被卖，如果confirm失败无论什么失败则重试(会依靠活动日志进行重试)</p>
<p>对于TCC来说适合一些:</p>
<ul>
<li>强隔离性，严格一致性要求的活动业务。</li>
<li>执行时间较短的业务</li>
</ul>
<p>实现参考:ByteTCC:<a href="https://link.juejin.cn/?target=https://github.com/liuyangming/ByteTCC/">github.com&#x2F;liuyangming…</a></p>
<h2 id="本地消息表"><a href="#本地消息表" class="headerlink" title="本地消息表"></a>本地消息表</h2><p>本地消息表这个方案最初是ebay提出的 ebay的完整方案<a target="_blank" rel="noopener" href="https://queue.acm.org/detail.cfm?id=1394128%E3%80%82">https://queue.acm.org/detail.cfm?id=1394128。</a></p>
<p>此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列，再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景，通过对账系统对事后问题的处理。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104309339.png" alt="image-20230704104309339"></p>
<p>对于本地消息队列来说核心是把大事务转变为小事务。还是举上面用100元去买一瓶水的例子。</p>
<p>1.当你扣钱的时候，你需要在你扣钱的服务器上新增加一个本地消息表，你需要把你扣钱和写入减去水的库存到本地消息表放入同一个事务(依靠数据库本地事务保证一致性。</p>
<p>2.这个时候有个定时任务去轮询这个本地事务表，把没有发送的消息，扔给商品库存服务器，叫他减去水的库存，到达商品服务器之后这个时候得先写入这个服务器的事务表，然后进行扣减，扣减成功后，更新事务表中的状态。</p>
<p>3.商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器，扣钱服务器本地消息表进行状态更新。</p>
<p>4.针对一些异常情况，定时扫描未成功处理的消息，进行重新发送，在商品服务器接到消息之后，首先判断是否是重复的，如果已经接收，在判断是否执行，如果执行在马上又进行通知事务，如果未执行，需要重新执行需要由业务保证幂等，也就是不会多扣一瓶水。</p>
<p>本地消息队列是BASE理论，是最终一致模型，适用于对一致性要求不高的。实现这个模型时需要注意重试的幂等。</p>
<h2 id="MQ事务"><a href="#MQ事务" class="headerlink" title="MQ事务"></a>MQ事务</h2><p>在RocketMQ中实现了分布式事务，实际上其实是对本地消息表的一个封装，将本地消息表移动到了MQ内部，下面简单介绍一下MQ事务，如果想对其详细了解可以参考: <a href="https://link.juejin.cn/?target=https://www.jianshu.com/p/453c6e7ff81c%E3%80%82">www.jianshu.com/p/453c6e7ff…</a></p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104652051.png" alt="image-20230704104652051"></p>
<p>基本流程如下: 第一阶段Prepared消息，会拿到消息的地址。</p>
<p>第二阶段执行本地事务。</p>
<p>第三阶段通过第一阶段拿到的地址去访问消息，并修改状态。消息接受者就能使用这个消息。</p>
<p>如果确认消息失败，在RocketMq Broker中提供了定时扫描没有更新状态的消息，如果有消息没有得到确认，会向消息发送者发送消息，来判断是否提交，在rocketmq中是以listener的形式给发送者，用来处理。</p>
<p><img src="https://cydmacro.gitee.io/myblog/img/image-20230704104729517.png" alt="image-20230704104729517"></p>
<p>如果消费超时，则需要一直重试，消息接收端需要保证幂等。如果消息消费失败，这个就需要人工进行处理，因为这个概率较低，如果为了这种小概率时间而设计这个复杂的流程反而得不偿失</p>
<h2 id="Saga事务"><a href="#Saga事务" class="headerlink" title="Saga事务"></a>Saga事务</h2><p>Saga是30年前一篇数据库伦理提到的一个概念。其核心思想是将长事务拆分为多个本地短事务，由Saga事务协调器协调，如果正常结束那就正常完成，如果某个步骤失败，则根据相反顺序一次调用补偿操作。 Saga的组成：</p>
<p>每个Saga由一系列sub-transaction Ti 组成 每个Ti 都有对应的补偿动作Ci，补偿动作用于撤销Ti造成的结果,这里的每个T，都是一个本地事务。 可以看到，和TCC相比，Saga没有“预留 try”动作，它的Ti就是直接提交到库。</p>
<p>Saga的执行顺序有两种：</p>
<p>T1, T2, T3, …, Tn</p>
<p>T1, T2, …, Tj, Cj,…, C2, C1，其中0 &lt; j &lt; n Saga定义了两种恢复策略：</p>
<p>向后恢复，即上面提到的第二种执行顺序，其中j是发生错误的sub-transaction，这种做法的效果是撤销掉之前所有成功的sub-transation，使得整个Saga的执行结果撤销。 向前恢复，适用于必须要成功的场景，执行顺序是类似于这样的：T1, T2, …, Tj(失败), Tj(重试),…, Tn，其中j是发生错误的sub-transaction。该情况下不需要Ci。</p>
<p>这里要注意的是，在saga模式中不能保证隔离性，因为没有锁住资源，其他事务依然可以覆盖或者影响当前事务。</p>
<p>还是拿100元买一瓶水的例子来说，这里定义</p>
<p>T1&#x3D;扣100元 T2&#x3D;给用户加一瓶水 T3&#x3D;减库存一瓶水</p>
<p>C1&#x3D;加100元 C2&#x3D;给用户减一瓶水 C3&#x3D;给库存加一瓶水</p>
<p>我们一次进行T1,T2，T3如果发生问题，就执行发生问题的C操作的反向。 上面说到的隔离性的问题会出现在，如果执行到T3这个时候需要执行回滚，但是这个用户已经把水喝了(另外一个事务)，回滚的时候就会发现，无法给用户减一瓶水了。这就是事务之间没有隔离性的问题</p>
<p>可以看见saga模式没有隔离性的影响还是较大，可以参照华为的解决方案:从业务层面入手加入一 Session 以及锁的机制来保证能够串行化操作资源。也可以在业务层面通过预先冻结资金的方式隔离这部分资源， 最后在业务操作的过程中可以通过及时读取当前状态的方式获取到最新的更新。</p>
<p>具体实例:可以参考华为的servicecomb</p>
<h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>还是那句话，能不用分布式事务就不用，如果非得使用的话，结合自己的业务分析，看看自己的业务比较适合哪一种，是在乎强一致，还是最终一致即可。上面对解决方案只是一些简单介绍，如果真正的想要落地，其实每种方案需要思考的地方都非常多，复杂度都比较大，所以最后再次提醒一定要判断好是否使用分布式事务。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-mac常用命令"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/07/04/mac%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/"
    >mac常用命令</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/07/04/mac%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/" class="article-date">
  <time datetime="2023-07-04T02:36:50.000Z" itemprop="datePublished">2023-07-04</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">记录一些mac下的常用命令：</span><br><span class="line"></span><br><span class="line">1、查看进程号</span><br><span class="line"></span><br><span class="line">ps -ef | grep 进程名</span><br><span class="line"></span><br><span class="line">2、查看端口被哪个进程监听</span><br><span class="line"></span><br><span class="line">sudo lsof -i :端口</span><br><span class="line"></span><br><span class="line">3、查看进程监听的端口</span><br><span class="line"></span><br><span class="line">sudo lsof -nP -p 进程号 | grep LISTEN</span><br><span class="line"></span><br><span class="line">sudo lsof -nP | grep LISTEN | grep 进程号</span><br><span class="line"></span><br><span class="line">4、查看监听端口的进程</span><br><span class="line"></span><br><span class="line">sudo lsof -nP | grep LISTEN | grep 端口号</span><br><span class="line"></span><br><span class="line">5、看到一个新的方法（MacOS统计TCP/UDP端口号与对应服务）</span><br><span class="line"></span><br><span class="line">echo &quot;### TCP LISTEN ###&quot;</span><br><span class="line">lsof -nP -iTCP -sTCP:LISTEN</span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-物联网架构"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/07/03/%E7%89%A9%E8%81%94%E7%BD%91%E6%9E%B6%E6%9E%84/"
    >物联网架构</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/07/03/%E7%89%A9%E8%81%94%E7%BD%91%E6%9E%B6%E6%9E%84/" class="article-date">
  <time datetime="2023-07-03T05:57:54.000Z" itemprop="datePublished">2023-07-03</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><img src="https://cydmacro.gitee.io/myblog/img/%E6%9E%B6%E6%9E%841.drawio.png" alt="架构1.drawio"></p>
<p>picgo图传上传图片终于可以了，开心，但是每次上传图片后都要重新发布比较麻烦，正在想办法解决中</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-部署开源erp"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/06/28/%E9%83%A8%E7%BD%B2%E5%BC%80%E6%BA%90erp/"
    >部署开源erp</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/06/28/%E9%83%A8%E7%BD%B2%E5%BC%80%E6%BA%90erp/" class="article-date">
  <time datetime="2023-06-28T09:25:58.000Z" itemprop="datePublished">2023-06-28</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p>公司要搞一套erp+酒吧点酒的系统，我想从头来做肯定来不及，所以找了几个开源的对比之下使用来这个erp</p>
<p><a href="mailto:&#103;&#105;&#x74;&#x40;&#103;&#105;&#x74;&#x65;&#x65;&#46;&#x63;&#111;&#109;">&#103;&#105;&#x74;&#x40;&#103;&#105;&#x74;&#x65;&#x65;&#46;&#x63;&#111;&#109;</a>:FINERME&#x2F;psi.git</p>
<p>部署比较容易，碰到2个问题，记录一下，以免以后想不起来</p>
<p>1、Mysql自定义函数报错解决方法</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">https://blog.csdn.net/CiWei007/article/details/15635151</span><br><span class="line"></span><br><span class="line">ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">解决方法：</span><br><span class="line"></span><br><span class="line">mysql&gt;set global log_bin_trust_function_creators=1;</span><br></pre></td></tr></table></figure>

<p>2、vue多版本问题</p>
<p>这个弄了好半天，因为我是mac m1   芯片，erp要求node 12.16.0 我本机之前安装了14 16 所以需求安装12，使用 n 12.16.0 安装一直失败，不知道是网络问题还是其他，放弃了；使用nvm 安装报错，各种折腾都不行，最后看到这篇文章解决了</p>
<p><a target="_blank" rel="noopener" href="https://github.com/nvm-sh/nvm/issues/2350">https://github.com/nvm-sh/nvm/issues/2350</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">arch -x86_64 zsh</span><br><span class="line">nvm install v12</span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-Docker安装Apollo"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/06/27/Docker%E5%AE%89%E8%A3%85Apollo/"
    >Docker安装Apollo</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/06/27/Docker%E5%AE%89%E8%A3%85Apollo/" class="article-date">
  <time datetime="2023-06-27T07:54:13.000Z" itemprop="datePublished">2023-06-27</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">什么是Apollo</span><br><span class="line">Apollo（阿波罗）是一款可靠的分布式配置管理中心，诞生于携程框架研发部，能够集中化管理应用不同环境、不同集群的配置，配置修改后能够实时推送到应用端，并且具备规范的权限、流程治理等特性，适用于微服务配置管理场景。</span><br><span class="line"></span><br><span class="line">Apollo支持4个维度管理Key-Value格式的配置：</span><br><span class="line"></span><br><span class="line">1 application (应用)</span><br><span class="line">2 environment (环境)</span><br><span class="line">3 cluster (集群)</span><br><span class="line">4 namespace (命名空间)</span><br><span class="line"></span><br><span class="line">同时，Apollo基于开源模式开发，开源地址：https://github.com/ctripcorp/apollo</span><br><span class="line"></span><br><span class="line">Apollo的特点</span><br><span class="line">统一管理不同环境，不同集群的配置</span><br><span class="line">配置实时更新</span><br><span class="line">版本发布</span><br><span class="line">灰度发布</span><br><span class="line">权限管理</span><br><span class="line">Apollo的核心</span><br><span class="line">application 应用</span><br><span class="line">environment 环境</span><br><span class="line">cluster 集群</span><br><span class="line">namespace 命名空间</span><br><span class="line">Apollo为什么采用Eureka</span><br><span class="line">Eureka是业界比较成熟的工具 并且是开源的 方便排查问题</span><br><span class="line">由于携程本身主要使用Java的SpringCloud和Boot 所以和Eureka集成更方便</span><br><span class="line">使用Docker安装Apollo</span><br><span class="line">安装Java jdk 1.8</span><br><span class="line"></span><br><span class="line">地址：https://www.java.com/zh-CN/download/</span><br><span class="line">查找dockerhub中mysql镜像</span><br><span class="line"></span><br><span class="line">docker search mysql</span><br><span class="line">docker pull --platform linux/x86_64 mysql</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/cydmacro/myblog/img/image-20230627160720533.png" alt="image-20230627160720533"></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-每日一扣"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/06/27/%E6%AF%8F%E6%97%A5%E4%B8%80%E6%89%A3/"
    >每日一扣</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/06/27/%E6%AF%8F%E6%97%A5%E4%B8%80%E6%89%A3/" class="article-date">
  <time datetime="2023-06-27T03:28:44.000Z" itemprop="datePublished">2023-06-27</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">第一道题：两数之和   难度：简单</span><br><span class="line">给定一个整数数组 nums 和一个整数目标值 target，请你在该数组中找出 和为目标值 target  的那 两个 整数，并返回它们的数组下标。</span><br><span class="line"></span><br><span class="line">你可以假设每种输入只会对应一个答案。但是，数组中同一个元素在答案里不能重复出现。</span><br><span class="line"></span><br><span class="line">你可以按任意顺序返回答案。</span><br><span class="line">示例 1：</span><br><span class="line"></span><br><span class="line">输入：nums = [2,7,11,15], target = 9</span><br><span class="line">输出：[0,1]</span><br><span class="line">解释：因为 nums[0] + nums[1] == 9 ，返回 [0, 1] 。</span><br><span class="line">示例 2：</span><br><span class="line"></span><br><span class="line">输入：nums = [3,2,4], target = 6</span><br><span class="line">输出：[1,2]</span><br><span class="line">示例 3：</span><br><span class="line"></span><br><span class="line">输入：nums = [3,3], target = 6</span><br><span class="line">输出：[0,1]</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">提示：</span><br><span class="line"></span><br><span class="line">2 &lt;= nums.length &lt;= 104</span><br><span class="line">-109 &lt;= nums[i] &lt;= 109</span><br><span class="line">-109 &lt;= target &lt;= 109</span><br><span class="line">只会存在一个有效答案</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">进阶：你可以想出一个时间复杂度小于 O(n2) 的算法吗？</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class Solution &#123;</span><br><span class="line">   public static  int[] twoSum(int[] nums, int target) &#123;</span><br><span class="line">        //存储结果临时变量</span><br><span class="line">        Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();</span><br><span class="line">        //循环遍历nums做减法</span><br><span class="line">        for(int i=0;i&lt;nums.length;i++)&#123;</span><br><span class="line">            //本质是 （target-目标值）是否出现在map中</span><br><span class="line">            if(map.containsKey(target - nums[i])) &#123;</span><br><span class="line">                return new int[] &#123;</span><br><span class="line">                        map.get(target-nums[i]),i</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;</span><br><span class="line">            //在没有找到目标值之前持续添加到map中</span><br><span class="line">            map.put(nums[i],i);</span><br><span class="line">        &#125;</span><br><span class="line">        throw new IllegalArgumentException(&quot;No two sum solution&quot;);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-加油继续不要放弃"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/06/20/%E5%8A%A0%E6%B2%B9%E7%BB%A7%E7%BB%AD%E4%B8%8D%E8%A6%81%E6%94%BE%E5%BC%83/"
    >加油继续不要放弃</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/06/20/%E5%8A%A0%E6%B2%B9%E7%BB%A7%E7%BB%AD%E4%B8%8D%E8%A6%81%E6%94%BE%E5%BC%83/" class="article-date">
  <time datetime="2023-06-20T15:35:23.000Z" itemprop="datePublished">2023-06-20</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">被罗翔老师的这段话骂醒了：</span><br><span class="line"></span><br><span class="line">“要么努力到出类拔萃，</span><br><span class="line">要么就懒得乐知天命，</span><br><span class="line">最怕你见识打开了，</span><br><span class="line">可努力又跟不上，</span><br><span class="line">骨子里清高至极，</span><br><span class="line">性格上软弱无比。〞</span><br><span class="line"></span><br><span class="line">我深怕自己本非美玉，</span><br><span class="line">故而不敢加以刻苦琢磨，</span><br><span class="line">却又半信自己是块美玉，</span><br><span class="line">故又不肯庸庸碌碌，与瓦砥为伍。</span><br><span class="line">于是我渐渐地脱离凡尘，</span><br><span class="line">疏远世人，</span><br><span class="line">结果便是一任愤懑与羞恨日益助长内心那怯弱的自尊心……</span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
    <article
  id="post-flink学习总结"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2023/06/17/flink%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/"
    >flink学习总结</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/06/17/flink%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/" class="article-date">
  <time datetime="2023-06-17T11:05:42.000Z" itemprop="datePublished">2023-06-17</time>
</a>    
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p>学习flink有一段时间了，这里做个小小的阶段性总结</p>
<h1 id="flink整体架构"><a href="#flink整体架构" class="headerlink" title="flink整体架构"></a>flink整体架构</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
    </footer>
  </div>

   
   
  
</article>

    
  </article>
  

  
  <nav class="page-nav">
    
    <a class="extend prev" rel="prev" href="/">上一页</a><a class="page-number" href="/">1</a><span class="page-number current">2</span><a class="page-number" href="/page/3/">3</a><a class="extend next" rel="next" href="/page/3/">下一页</a>
  </nav>
  
</section>
</div>

      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2023
        <i class="ri-heart-fill heart_icon"></i> cyd
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="风舞神者"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E6%97%85%E8%A1%8C/">旅行</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://shenyu-vip.lofter.com">摄影</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-3.6.0.min.js"></script>
 
<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->

<script src="https://cdn.staticfile.org/jquery-modal/0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.staticfile.org/jquery-modal/0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.staticfile.org/justifiedGallery/3.8.1/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.staticfile.org/photoswipe/4.1.3/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.staticfile.org/photoswipe/4.1.3/default-skin/default-skin.min.css">
<script src="https://cdn.staticfile.org/photoswipe/4.1.3/photoswipe.min.js"></script>
<script src="https://cdn.staticfile.org/photoswipe/4.1.3/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/css/clipboard.css">
 <script src="https://cdn.staticfile.org/clipboard.js/2.0.10/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->

<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>